push aplikacie na git

This commit is contained in:
Michal Utľák 2026-03-26 16:43:19 +01:00
parent 0d86f0058e
commit 7e536c7d74
25 changed files with 3344 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

661
Backend/app.py Normal file
View File

@ -0,0 +1,661 @@
from flask import Flask, jsonify, send_file, request
from flask_cors import CORS
import threading
import multiprocessing as mp
import gc
import traceback
import json
import time
import os
import platform
import psutil
import traceback
try:
import winreg
except ImportError:
winreg = None
from datetime import datetime
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from isolation_forest_cicids import run_isolation_forest
from autoencoder import run_autoencoder
from isolation_forest_custom import run_isolation_forest_custom
from autoencoder_custom import run_autoencoder_custom
import signal
import sys
import shutil
from flask import send_from_directory
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEMP_DIR = os.path.join(BASE_DIR, "temp")
REPORT_DIR = os.path.join(BASE_DIR, "reports")
PROGRESS_PATH = os.path.join(BASE_DIR, "progress.json")
FRONTEND_DIR = os.path.join(BASE_DIR, "..", "Frontend")
HTML_DIR = os.path.join(FRONTEND_DIR, "HTML Files")
JS_DIR = os.path.join(FRONTEND_DIR, "JS")
IMG_DIR = os.path.join(FRONTEND_DIR, "img")
os.makedirs(TEMP_DIR, exist_ok=True)
os.makedirs(REPORT_DIR, exist_ok=True)
app = Flask(__name__)
CORS(app)
def force_memory_cleanup():
gc.collect()
try:
import ctypes
ctypes.CDLL("libc.so.6").malloc_trim(0)
except Exception:
pass
@app.route("/", methods=["GET"])
def serve_index():
return send_from_directory(HTML_DIR, "index.html")
@app.route("/upload", methods=["GET"])
def serve_upload():
return send_from_directory(HTML_DIR, "upload.html")
@app.route("/default", methods=["GET"])
def serve_default():
return send_from_directory(HTML_DIR, "default.html")
@app.route("/JS/<path:filename>", methods=["GET"])
def serve_js(filename):
return send_from_directory(JS_DIR, filename)
@app.route("/img/<path:filename>", methods=["GET"])
def serve_img(filename):
return send_from_directory(IMG_DIR, filename)
model_result_iforest = None
model_result_autoencoder = None
model_result_iforest_custom = None
model_result_autoencoder_custom = None
state_lock = threading.Lock()
is_running = False
current_job = None
worker_process = None
RESULT_IFOREST = os.path.join(TEMP_DIR, "result_iforest.json")
RESULT_AUTOENCODER = os.path.join(TEMP_DIR, "result_autoencoder.json")
RESULT_IFOREST_CUSTOM = os.path.join(TEMP_DIR, "result_iforest_custom.json")
RESULT_AUTOENCODER_CUSTOM = os.path.join(TEMP_DIR, "result_autoencoder_custom.json")
def handle_exit(signum, frame):
cleanup()
sys.exit(0)
signal.signal(signal.SIGINT, handle_exit)
signal.signal(signal.SIGTERM, handle_exit)
def clear_directory(path):
if not os.path.exists(path):
return
for item in os.listdir(path):
item_path = os.path.join(path, item)
try:
if os.path.isfile(item_path) or os.path.islink(item_path):
os.unlink(item_path)
elif os.path.isdir(item_path):
shutil.rmtree(item_path)
except Exception as e:
print(f"Failed to delete {item_path}: {e}")
def cleanup():
clear_directory(TEMP_DIR)
clear_directory(REPORT_DIR)
def update_progress(value):
with open(PROGRESS_PATH, "w") as f:
json.dump({"progress": value}, f)
def read_json_file(path, default=None):
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
return default
def write_json_file(path, data):
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
def get_result_path(job_name):
mapping = {
"iforest": RESULT_IFOREST,
"autoencoder": RESULT_AUTOENCODER,
"iforest_custom": RESULT_IFOREST_CUSTOM,
"autoencoder_custom": RESULT_AUTOENCODER_CUSTOM,
}
return mapping[job_name]
def get_job_runner(job_name):
if job_name == "iforest":
return lambda: run_isolation_forest(plot=False, table=False)
elif job_name == "autoencoder":
return lambda: run_autoencoder(plot=False, table=False)
elif job_name == "iforest_custom":
return run_isolation_forest_custom
elif job_name == "autoencoder_custom":
return run_autoencoder_custom
else:
raise ValueError(f"Unknown job name: {job_name}")
def worker_entry(job_name):
result_path = get_result_path(job_name)
start = time.time()
try:
update_progress(1)
runner = get_job_runner(job_name)
result = runner()
end = time.time()
if isinstance(result, dict):
result["runtime"] = round(end - start, 2)
write_json_file(result_path, {
"ready": True,
"data": result
})
update_progress(100)
except Exception as e:
write_json_file(result_path, {
"ready": True,
"data": {
"error": True,
"message": str(e),
"details": traceback.format_exc()
}
})
update_progress(0)
def refresh_worker_state():
global is_running, worker_process, current_job
with state_lock:
proc = worker_process
running = is_running
if running and proc is not None and not proc.is_alive():
try:
proc.join(timeout=0.2)
except Exception:
pass
try:
proc.close()
except Exception:
pass
with state_lock:
is_running = False
worker_process = None
current_job = None
force_memory_cleanup()
def start_background_job(job_name):
global is_running, current_job, worker_process
result_path = get_result_path(job_name)
refresh_worker_state()
with state_lock:
if is_running:
return False
is_running = True
current_job = job_name
if os.path.exists(result_path):
try:
os.remove(result_path)
except Exception:
pass
update_progress(0)
proc = mp.Process(target=worker_entry, args=(job_name,))
proc.start()
with state_lock:
worker_process = proc
return True
def read_progress():
try:
with open(PROGRESS_PATH, "r") as f:
return json.load(f).get("progress", 0)
except Exception:
return 0
update_progress(0)
@app.route("/start-iforest", methods=["POST"])
def start_iforest():
started = start_background_job("iforest")
if not started:
return jsonify({
"status": "already_running",
"message": "Another algorithm is already running"
}), 409
return jsonify({"status": "started"})
@app.route("/start-iforest-custom", methods=["POST"])
def start_iforest_custom():
started = start_background_job("iforest_custom")
if not started:
return jsonify({
"status": "already_running",
"message": "Another algorithm is already running"
}), 409
return jsonify({"status": "started"})
@app.route("/start-autoencoder-custom", methods=["POST"])
def start_autoencoder_custom():
started = start_background_job("autoencoder_custom")
if not started:
return jsonify({
"status": "already_running",
"message": "Another algorithm is already running"
}), 409
return jsonify({"status": "started"})
@app.route("/start-autoencoder", methods=["POST"])
def start_autoencoder():
started = start_background_job("autoencoder")
if not started:
return jsonify({
"status": "already_running",
"message": "Another algorithm is already running"
}), 409
return jsonify({"status": "started"})
@app.route("/get-progress", methods=["GET"])
def get_progress():
refresh_worker_state()
return jsonify(read_json_file(PROGRESS_PATH, {"progress": 0}))
@app.route("/get-status", methods=["GET"])
def get_status():
refresh_worker_state()
with state_lock:
running = is_running
job = current_job
progress_data = read_json_file(PROGRESS_PATH, {"progress": 0})
return jsonify({
"running": running,
"current_job": job,
"progress": progress_data.get("progress", 0)
})
@app.route("/get-isolation-forest-result", methods=["GET"])
def get_iforest_result():
refresh_worker_state()
data = read_json_file(RESULT_IFOREST)
if not data:
return jsonify({"ready": False})
return jsonify(data)
@app.route("/get-isolation-forest-result-custom", methods=["GET"])
def get_iforest_result_custom():
refresh_worker_state()
data = read_json_file(RESULT_IFOREST_CUSTOM)
if not data:
return jsonify({"ready": False})
return jsonify(data)
@app.route("/get-autoencoder-result", methods=["GET"])
def get_auto_result():
refresh_worker_state()
data = read_json_file(RESULT_AUTOENCODER)
if not data:
return jsonify({"ready": False})
return jsonify(data)
@app.route("/get-autoencoder-result-custom", methods=["GET"])
def get_autoencoder_result_custom():
refresh_worker_state()
data = read_json_file(RESULT_AUTOENCODER_CUSTOM)
if not data:
return jsonify({"ready": False})
return jsonify(data)
def generate_pdf_report(results, algorithm, params):
filename = os.path.join(REPORT_DIR, "report.pdf")
c = canvas.Canvas(filename, pagesize=A4)
w, h = A4
y = h - 2*cm
c.setFont("Helvetica-Bold", 24)
c.drawCentredString(w/2, y, f"{algorithm} Report")
y -= 1.4*cm
c.setFont("Helvetica", 11)
c.drawCentredString(
w/2, y,
f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
)
y -= 1.6*cm
c.setFont("Helvetica-Bold", 15)
c.drawString(2*cm, y, "Algorithm Parameters:")
y -= 0.9*cm
c.setFont("Helvetica", 11)
t = c.beginText(2.2*cm, y)
t.setLeading(13)
for k, v in params.items():
t.textLine(f"{k}: {v}")
c.drawText(t)
y = t.getY() - 1.1*cm
c.setFont("Helvetica-Bold", 15)
c.drawString(2*cm, y, "Execution Summary:")
y -= 0.9*cm
c.setFont("Helvetica", 11)
t = c.beginText(2.2*cm, y)
t.setLeading(13)
t.textLine(f"Runtime: {results['runtime']} seconds")
t.textLine(f"Normal Traffic: {int(results['normal_count'])}")
t.textLine(f"Detected Attacks: {int(results['attack_count'])}")
t.textLine(f"Accuracy: {results['accuracy']:.4f}")
c.drawText(t)
y = t.getY() - 1.3*cm
sys = get_system_info()
c.setFont("Helvetica-Bold", 15)
c.drawString(2 * cm, y, "System Information")
y -= 0.9 * cm
c.setFont("Helvetica", 11)
info = c.beginText(2.2 * cm, y)
info.setLeading(13)
info.textLine(f"CPU: {sys['cpu']}")
info.textLine(f"Physical Cores: {sys['cpu_physical_cores']}")
info.textLine(f"Logical Cores: {sys['cpu_total_cores']}")
info.textLine(f"GPU: {sys['gpu_name']}")
info.textLine(f"OS: {sys['os']}")
info.textLine(f"Python Version: {sys['python_version']}")
info.textLine(f"System RAM: {sys['ram_total_gb']} GB")
c.drawText(info)
y = info.getY() - 1.3 * cm
c.setFont("Helvetica-Bold", 15)
c.drawString(2 * cm, y, "Memory Usage:")
y -= 0.9 * cm
c.setFont("Helvetica", 11)
t = c.beginText(2.2 * cm, y)
t.setLeading(13)
t.textLine(f"RAM Before: {results['ram_before']} MB")
t.textLine(f"Peak RAM: {results['ram_peak']} MB")
t.textLine(f"RAM After: {results['ram_after']} MB")
t.textLine(f"Total Increase: {results['ram_increase']} MB")
c.drawText(t)
y = t.getY() - 1.1 * cm
c.setFont("Helvetica-Bold", 15)
c.drawString(2*cm, y, "Top 5 Most Significant Anomalies:")
y -= 1*cm
c.setFont("Helvetica", 11)
for anomaly in results["top_anomalies"]:
t = c.beginText(2.4*cm, y)
t.setLeading(13)
for k, v in anomaly.items():
t.textLine(f"{k}: {v}")
c.drawText(t)
y = t.getY() - 0.9*cm
if y < 3*cm:
c.showPage()
y = h - 2*cm
c.setFont("Helvetica", 11)
c.save()
return filename
@app.route("/download-pdf", methods=["POST"])
def download_pdf():
data = request.get_json()
algorithm = data["algorithm"]
results = data["results"]
raw_algorithm = data["algorithm"]
algorithm_name = raw_algorithm.lower()
if "isolation forest" in algorithm_name:
algo_title = "Isolation Forest"
params = {
"n_estimators": 600,
"max_samples": 0.3,
"contamination": results.get("contamination"),
"max_features": 0.7
}
elif "autoencoder" in algorithm_name:
algo_title = "Autoencoder"
params = {
"epochs": 20,
"batch_size": 64,
"latent_dimension": 16,
"validation_split": 0.1
}
else:
params = {}
pdf_path = generate_pdf_report(results, algo_title, params)
return send_file(pdf_path, as_attachment=True)
@app.route("/get-system-info", methods=["GET"])
def system_info_endpoint():
return jsonify(get_system_info())
def get_cpu_name():
try:
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"HARDWARE\DESCRIPTION\System\CentralProcessor\0"
)
cpu_name, _ = winreg.QueryValueEx(key, "ProcessorNameString")
winreg.CloseKey(key)
return cpu_name.strip()
except:
return platform.processor() or "Unknown CPU"
def get_system_info():
return {
"cpu": get_cpu_name(),
"cpu_physical_cores": psutil.cpu_count(logical=False),
"cpu_total_cores": psutil.cpu_count(logical=True),
"gpu_available": False,
"gpu_name": "None",
"os": platform.platform(),
"python_version": platform.python_version(),
"ram_total_gb": round(psutil.virtual_memory().total / (1024**3), 2),
"ram_used_mb": round(psutil.virtual_memory().used / (1024**2), 2),
}
@app.route("/download-json", methods=["POST"])
def download_json():
data = request.get_json()
algorithm = data["algorithm"]
results = data["results"]
runtime = data["runtime"]
raw_algorithm = data["algorithm"]
algorithm_name = raw_algorithm.lower()
if "isolation forest" in algorithm_name:
algo_title = "Isolation Forest"
params = {
"n_estimators": 600,
"max_samples": 0.3,
"contamination": results.get("contamination"),
"max_features": 0.7
}
elif "autoencoder" in algorithm_name:
algo_title = "Autoencoder"
params = {
"epochs": 20,
"batch_size": 64,
"latent_dimension": 16,
"validation_split": 0.1
}
system_info = get_system_info()
ram_info = {
"ram_before": results["ram_before"],
"ram_peak": results["ram_peak"],
"ram_after": results["ram_after"],
"ram_increase": results["ram_increase"]
}
anomalies = results.get("top_anomalies", [])
json_report = {
"algorithm": algorithm,
"generated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"algorithm_parameters": params,
"execution_summary": {
"runtime_seconds": runtime,
"normal_traffic": results["normal_count"],
"detected_attacks": results["attack_count"],
"accuracy": results["accuracy"]
},
"system_information": system_info,
"memory_usage": ram_info,
"top_5_anomalies": anomalies
}
json_path = os.path.join(REPORT_DIR, "report.json")
with open(json_path, "w", encoding="utf-8") as f:
json.dump(json_report, f, indent=4)
return send_file(json_path, as_attachment=True)
@app.route("/upload-dataset", methods=["POST"])
def upload_dataset():
refresh_worker_state()
with state_lock:
if is_running:
return jsonify({
"error": "Cannot upload a new dataset while an algorithm is running"
}), 409
if "file" not in request.files:
return jsonify({"error": "No file provided"}), 400
file = request.files["file"]
if file.filename == "":
return jsonify({"error": "Empty filename"}), 400
if not file.filename.lower().endswith(".csv"):
return jsonify({"error": "Only CSV files are allowed"}), 400
csv_path = os.path.join(TEMP_DIR, "upload.csv")
tmp_path = os.path.join(TEMP_DIR, "upload.tmp.csv")
try:
if os.path.exists(tmp_path):
os.remove(tmp_path)
file.save(tmp_path)
os.replace(tmp_path, csv_path)
return jsonify({"status": "uploaded", "path": csv_path})
finally:
try:
file.close()
except Exception:
pass
try:
if hasattr(file, "stream") and file.stream:
file.stream.close()
except Exception:
pass
force_memory_cleanup()
@app.route("/save-config", methods=["POST"])
def save_config():
refresh_worker_state()
with state_lock:
if is_running:
return jsonify({
"error": "Cannot save config while an algorithm is running"
}), 409
data = request.get_json()
if data is None:
return jsonify({"error": "No JSON provided"}), 400
config_path = os.path.join(TEMP_DIR, "config.json")
with open(config_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
force_memory_cleanup()
return jsonify({"status": "config_saved"})
if __name__ == "__main__":
mp.freeze_support()
app.run(host="0.0.0.0", port=5000, debug=False, use_reloader=False)

223
Backend/autoencoder.py Normal file
View File

@ -0,0 +1,223 @@
import warnings
warnings.filterwarnings("ignore")
import psutil
import pandas as pd
import numpy as np
import json
import gc
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import (
confusion_matrix,
accuracy_score,
precision_recall_fscore_support,
)
from tabulate import tabulate
import os
def _force_memory_cleanup():
gc.collect()
try:
import ctypes
ctypes.CDLL("libc.so.6").malloc_trim(0)
except Exception:
pass
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
PROGRESS_PATH = os.path.join(BASE_DIR, "progress.json")
DATASET_PATH = os.path.join(BASE_DIR, "dataset", "cicids2017_cleaned.csv")
def update_progress(value):
with open(PROGRESS_PATH, "w") as f:
json.dump({"progress": value}, f)
def run_autoencoder(csv_path=DATASET_PATH, plot=False, table=False):
import tensorflow as tf
from tensorflow.keras import Model
from tensorflow.keras.layers import Dense, Input, Dropout
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras import backend as K
process = psutil.Process()
ram_before = process.memory_info().rss
ram_peak = ram_before
update_progress(1)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
df = pd.read_csv(csv_path)
update_progress(10)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
df["fraud"] = df["Attack Type"].apply(
lambda x: 0 if "normal" in str(x).lower() else 1
)
true_labels = df["fraud"]
normal_count = int((true_labels == 0).sum())
attack_count = int((true_labels == 1).sum())
features = [c for c in df.columns if c not in ["Attack Type", "fraud"]]
scaler = StandardScaler()
X_raw = df[features].to_numpy(dtype=np.float32, copy=True)
y = df["fraud"].to_numpy(dtype=np.int8, copy=True)
X_scaled = scaler.fit_transform(X_raw).astype(np.float32, copy=False)
X_train_normal = X_scaled[y == 0]
X_test = X_scaled
y_test = y
CODE_DIM = 16
INPUT_SHAPE = X_scaled.shape[1]
inp = Input(shape=(INPUT_SHAPE,))
x = Dense(128, activation="relu")(inp)
x = Dropout(0.1)(x)
x = Dense(64, activation="relu")(x)
x = Dense(16, activation="relu")(x)
code = Dense(CODE_DIM, activation="relu")(x)
x = Dense(16, activation="relu")(code)
x = Dense(64, activation="relu")(x)
x = Dense(128, activation="relu")(x)
out = Dense(INPUT_SHAPE, activation="linear")(x)
autoencoder = Model(inp, out)
autoencoder.compile(loss="mae", optimizer=Adam(learning_rate=0.001))
update_progress(40)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
earlystopping = EarlyStopping(
monitor="val_loss", patience=5, restore_best_weights=True
)
history = autoencoder.fit(
X_train_normal,
X_train_normal,
epochs=20,
batch_size=64,
validation_split=0.1,
callbacks=[earlystopping],
shuffle=True,
verbose=1,
)
update_progress(60)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
reconstructions = autoencoder.predict(X_test, verbose=0)
reconstruction_error = np.mean(np.abs(reconstructions - X_test), axis=1)
recons_df = pd.DataFrame(
{"error": reconstruction_error, "y_true": y_test}
).reset_index(drop=True)
threshold = np.percentile(recons_df["error"], 60)
recons_df["y_pred"] = (recons_df["error"] > threshold).astype(int)
update_progress(80)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
cm = confusion_matrix(recons_df["y_true"], recons_df["y_pred"])
accuracy = accuracy_score(recons_df["y_true"], recons_df["y_pred"])
precision, recall, f1, _ = precision_recall_fscore_support(
recons_df["y_true"],
recons_df["y_pred"],
average=None,
labels=[0, 1],
)
update_progress(90)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
if table:
table_data = [
["Normal (0)", f"{precision[0]:.4f}", f"{recall[0]:.4f}", f"{f1[0]:.4f}"],
["Attack (1)", f"{precision[1]:.4f}", f"{recall[1]:.4f}", f"{f1[1]:.4f}"],
["Overall Accuracy", "-", "-", f"{accuracy:.4f}"],
]
print(
tabulate(
table_data,
headers=["Class", "Precision", "Recall", "F1-Score"],
tablefmt="fancy_grid",
)
)
results = {
"normal_count": float(normal_count),
"attack_count": float(attack_count),
"accuracy": float(accuracy),
"precision_normal": float(precision[0]),
"recall_normal": float(recall[0]),
"f1_normal": float(f1[0]),
"precision_attack": float(precision[1]),
"recall_attack": float(recall[1]),
"f1_attack": float(f1[1]),
}
candidates = recons_df[
(recons_df["y_pred"] == 1) & (recons_df["y_true"] == 1)
].copy()
if len(candidates) < 5:
extra = recons_df[recons_df["y_pred"] == 1].copy()
candidates = pd.concat([candidates, extra]).drop_duplicates()
if len(candidates) < 5:
candidates = recons_df.copy()
candidates = candidates.sort_values("error", ascending=False).head(5)
idx = candidates.index
df_top = df.iloc[idx].copy()
df_top["reconstruction_error"] = candidates["error"].values
important_cols = [
"Attack Type",
"Destination Port",
"Flow Duration",
"Total Fwd Packets",
"Flow Packets/s",
"Packet Length Mean",
]
cols_exist = [c for c in important_cols if c in df_top.columns]
top_anomalies = df_top[cols_exist + ["reconstruction_error"]].to_dict(
orient="records"
)
results["top_anomalies"] = top_anomalies
del X_raw, X_scaled, X_train_normal, X_test, y_test, y
del reconstructions, reconstruction_error, recons_df, candidates, df_top
del autoencoder, history, scaler, df
K.clear_session()
tf.keras.backend.clear_session(free_memory=True)
_force_memory_cleanup()
ram_after = process.memory_info().rss
results["ram_before"] = round(ram_before / (1024 ** 2), 2)
results["ram_peak"] = round(ram_peak / (1024 ** 2), 2)
results["ram_after"] = round(ram_after / (1024 ** 2), 2)
results["ram_increase"] = round((ram_peak - ram_before) / (1024 ** 2), 2)
update_progress(100)
return results
if __name__ == "__main__":
res = run_autoencoder(plot=True, table=True)
print(res)

View File

@ -0,0 +1,240 @@
# autoencoder_custom.py
import warnings
warnings.filterwarnings("ignore")
import json
import pandas as pd
import numpy as np
import psutil
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import (
confusion_matrix,
accuracy_score,
precision_recall_fscore_support,
)
import os
import gc
def _force_memory_cleanup():
gc.collect()
try:
import ctypes
ctypes.CDLL("libc.so.6").malloc_trim(0)
except Exception:
pass
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEMP_DIR = os.path.join(BASE_DIR, "temp")
def update_progress(value, progress_path="progress.json"):
with open(progress_path, "w") as f:
json.dump({"progress": value}, f)
def _update_ram_peak(process, ram_peak):
current_ram = process.memory_info().rss
return max(ram_peak, current_ram)
def run_autoencoder_custom(csv_path=None, config_path=None, progress_path=None):
if csv_path is None:
csv_path = os.path.join(TEMP_DIR, "upload.csv")
if config_path is None:
config_path = os.path.join(TEMP_DIR, "config.json")
if progress_path is None:
progress_path = os.path.join(BASE_DIR, "progress.json")
process = psutil.Process()
_force_memory_cleanup()
ram_before = process.memory_info().rss
ram_peak = ram_before
update_progress(5, progress_path)
import tensorflow as tf
from tensorflow.keras import Model
from tensorflow.keras.layers import Dense, Input, Dropout
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras import backend as K
ram_peak = _update_ram_peak(process, ram_peak)
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
ram_peak = _update_ram_peak(process, ram_peak)
label_col = config["labeling"]["label_column"]
normal_value = config["labeling"]["normal_value"]
features = config["features"]["selected_columns"]
params = config.get("algorithm", {}).get("parameters", {})
needed_cols = list(dict.fromkeys(features + [label_col]))
df = pd.read_csv(csv_path, usecols=needed_cols)
update_progress(15, progress_path)
ram_peak = _update_ram_peak(process, ram_peak)
col = df[label_col]
df["__label"] = col.apply(lambda x: 0 if x == normal_value else 1)
update_progress(25, progress_path)
ram_peak = _update_ram_peak(process, ram_peak)
normal_count = int((df["__label"] == 0).sum())
attack_count = int((df["__label"] == 1).sum())
X_raw = df[features].to_numpy(dtype=np.float32, copy=True)
y = df["__label"].to_numpy(dtype=np.int8, copy=True)
ram_peak = _update_ram_peak(process, ram_peak)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_raw).astype(np.float32, copy=False)
ram_peak = _update_ram_peak(process, ram_peak)
X_train_normal = X_scaled[y == 0]
X_test = X_scaled
y_test = y
ram_peak = _update_ram_peak(process, ram_peak)
INPUT_SHAPE = X_scaled.shape[1]
CODE_DIM = params.get("latent_dim", 16)
inp = Input(shape=(INPUT_SHAPE,))
x = Dense(128, activation="relu")(inp)
x = Dropout(0.1)(x)
x = Dense(64, activation="relu")(x)
x = Dense(16, activation="relu")(x)
code = Dense(CODE_DIM, activation="relu")(x)
x = Dense(16, activation="relu")(code)
x = Dense(64, activation="relu")(x)
x = Dense(128, activation="relu")(x)
out = Dense(INPUT_SHAPE, activation="linear")(x)
autoencoder = Model(inp, out)
autoencoder.compile(
loss="mae",
optimizer=Adam(learning_rate=params.get("learning_rate", 0.001))
)
update_progress(45, progress_path)
ram_peak = _update_ram_peak(process, ram_peak)
earlystopping = EarlyStopping(
monitor="val_loss", patience=5, restore_best_weights=True
)
history = autoencoder.fit(
X_train_normal,
X_train_normal,
epochs=params.get("epochs", 20),
batch_size=params.get("batch_size", 64),
validation_split=params.get("validation_split", 0.1),
callbacks=[earlystopping],
shuffle=True,
verbose=1,
)
update_progress(75, progress_path)
ram_peak = _update_ram_peak(process, ram_peak)
reconstructions = autoencoder.predict(X_test, verbose=0)
ram_peak = _update_ram_peak(process, ram_peak)
reconstruction_error = np.mean(np.abs(reconstructions - X_test), axis=1)
ram_peak = _update_ram_peak(process, ram_peak)
recons_df = pd.DataFrame(
{"error": reconstruction_error, "y_true": y_test}
).reset_index(drop=True)
ram_peak = _update_ram_peak(process, ram_peak)
threshold = np.percentile(recons_df["error"], params.get("threshold_percentile", 60))
recons_df["y_pred"] = (recons_df["error"] > threshold).astype(int)
update_progress(85, progress_path)
ram_peak = _update_ram_peak(process, ram_peak)
cm = confusion_matrix(recons_df["y_true"], recons_df["y_pred"])
accuracy = accuracy_score(recons_df["y_true"], recons_df["y_pred"])
precision, recall, f1, _ = precision_recall_fscore_support(
recons_df["y_true"],
recons_df["y_pred"],
average=None,
labels=[0, 1],
)
results = {
"normal_count": float(normal_count),
"attack_count": float(attack_count),
"accuracy": float(accuracy),
"precision_normal": float(precision[0]),
"recall_normal": float(recall[0]),
"f1_normal": float(f1[0]),
"precision_attack": float(precision[1]),
"recall_attack": float(recall[1]),
"f1_attack": float(f1[1]),
}
display_cols = list(dict.fromkeys(features + [label_col]))
top_df = df[display_cols].copy()
top_df["reconstruction_error"] = recons_df["error"].to_numpy()
top_df["predicted"] = recons_df["y_pred"].to_numpy()
top_df["true_label"] = y_test
ram_peak = _update_ram_peak(process, ram_peak)
real_attacks = top_df[top_df["true_label"] == 1]
real_attacks_sorted = real_attacks.sort_values("reconstruction_error", ascending=False)
top_real_attacks = real_attacks_sorted.head(5)
ram_peak = _update_ram_peak(process, ram_peak)
if len(top_real_attacks) > 0:
cols = [c for c in display_cols if c in top_real_attacks.columns] + ["reconstruction_error"]
top_anomalies = (
top_real_attacks[cols]
.rename(columns={"reconstruction_error": "score"})
.round(4)
.to_dict(orient="records")
)
else:
top_anomalies = []
results["top_anomalies"] = top_anomalies
ram_peak = _update_ram_peak(process, ram_peak)
del X_raw, X_scaled, X_train_normal, X_test, y_test, y
del reconstructions, reconstruction_error, recons_df
del top_df, real_attacks, real_attacks_sorted, top_real_attacks
del autoencoder, history, scaler, df, cm
try:
K.clear_session()
except Exception:
pass
try:
tf.keras.backend.clear_session(free_memory=True)
except TypeError:
tf.keras.backend.clear_session()
except Exception:
pass
_force_memory_cleanup()
ram_after = process.memory_info().rss
results["ram_before"] = round(ram_before / (1024 ** 2), 2)
results["ram_peak"] = round(ram_peak / (1024 ** 2), 2)
results["ram_after"] = round(ram_after / (1024 ** 2), 2)
results["ram_increase"] = round((ram_peak - ram_before) / (1024 ** 2), 2)
update_progress(100, progress_path)
return results
if __name__ == "__main__":
res = run_autoencoder_custom()
print(json.dumps(res, indent=2))

Binary file not shown.
Can't render this file because it is too large.

View File

@ -0,0 +1,211 @@
import pandas as pd
import numpy as np
import json
import psutil
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import RobustScaler
from sklearn.metrics import confusion_matrix, accuracy_score, precision_recall_fscore_support
from tabulate import tabulate
import matplotlib.pyplot as plt
import seaborn as sns
import gc
import os
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
PROGRESS_PATH = os.path.join(BASE_DIR, "progress.json")
DATASET_PATH = os.path.join(BASE_DIR, "dataset", "cicids2017_cleaned.csv")
def _force_memory_cleanup():
gc.collect()
try:
import ctypes
ctypes.CDLL("libc.so.6").malloc_trim(0)
except Exception:
pass
def update_progress(value):
with open(PROGRESS_PATH, "w") as f:
json.dump({"progress": value}, f)
def run_isolation_forest(csv_path=DATASET_PATH, plot=False, table=False):
process = psutil.Process()
ram_before = process.memory_info().rss
ram_peak = ram_before
update_progress(1)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
df = pd.read_csv(csv_path)
update_progress(10)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
df["fraud"] = df["Attack Type"].apply(
lambda x: 0 if "normal" in str(x).lower() else 1
)
true_labels = df["fraud"]
update_progress(20)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
normal_count = int((true_labels == 0).sum())
attack_count = int((true_labels == 1).sum())
attack_fraction = true_labels.mean()
contamination = min(attack_fraction * 2.5, 0.49)
X = df.select_dtypes(include=[np.number])
X = X.loc[:, X.std() > 0.01]
X = X.to_numpy(dtype=np.float32, copy=True)
update_progress(30)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X).astype(np.float32, copy=False)
update_progress(40)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
model = IsolationForest(
n_estimators=600,
max_samples=0.3,
contamination=contamination,
max_features=0.7,
bootstrap=False,
random_state=42,
n_jobs=1,
)
model.fit(X_scaled)
update_progress(70)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
preds = model.predict(X_scaled)
df["pred_label"] = np.where(preds == 1, 0, 1)
df["anomaly_score"] = model.decision_function(X_scaled)
update_progress(85)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
cm = confusion_matrix(true_labels, df["pred_label"])
accuracy = accuracy_score(true_labels, df["pred_label"])
precision, recall, f1, _ = precision_recall_fscore_support(
true_labels, df["pred_label"], average=None, labels=[0, 1]
)
update_progress(95)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
if table:
table_data = [
["Normal (0)", f"{precision[0]:.4f}", f"{recall[0]:.4f}", f"{f1[0]:.4f}"],
["Attack (1)", f"{precision[1]:.4f}", f"{recall[1]:.4f}", f"{f1[1]:.4f}"],
["Overall Accuracy", "-", "-", f"{accuracy:.4f}"],
]
print(
tabulate(
table_data,
headers=["Class", "Precision", "Recall", "F1-Score"],
tablefmt="fancy_grid",
)
)
if plot:
plt.figure(figsize=(10, 5))
scatter = plt.scatter(
range(len(df)),
df["anomaly_score"],
c=df["pred_label"],
cmap="coolwarm",
s=10,
)
plt.xlabel("Instance")
plt.ylabel("Anomaly Score")
plt.title("Anomaly Score Distribution (Isolation Forest)")
handles, labels = scatter.legend_elements()
plt.legend(handles, ["Normal", "Anomaly"], title="Predicted")
plt.show()
plt.figure(figsize=(5, 4))
sns.heatmap(
cm,
annot=True,
fmt="d",
cmap="Blues",
xticklabels=["Normal", "Attack"],
yticklabels=["Normal", "Attack"],
)
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix (Isolation Forest)")
plt.show()
results = {
"normal_count": float(normal_count),
"attack_count": float(attack_count),
"contamination": float(contamination),
"accuracy": float(accuracy),
"precision_normal": float(precision[0]),
"recall_normal": float(recall[0]),
"f1_normal": float(f1[0]),
"precision_attack": float(precision[1]),
"recall_attack": float(recall[1]),
"f1_attack": float(f1[1]),
}
candidates = df[(df["pred_label"] == 1) & (df["fraud"] == 1)].copy()
if len(candidates) < 5:
extra = df[df["pred_label"] == 1].copy()
candidates = pd.concat([candidates, extra]).drop_duplicates()
if len(candidates) < 5:
candidates = df.copy()
candidates = candidates.sort_values("anomaly_score").head(5)
important_cols = [
"Attack Type",
"Destination Port",
"Flow Duration",
"Total Fwd Packets",
"Flow Packets/s",
"Packet Length Mean",
]
cols_exist = [c for c in important_cols if c in candidates.columns]
top_anomalies = candidates[cols_exist + ["anomaly_score"]].rename(
columns={"anomaly_score": "score"}
).to_dict(orient="records")
results["top_anomalies"] = top_anomalies
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
del X, X_scaled, preds, model, scaler, candidates, df, cm, true_labels
_force_memory_cleanup()
ram_after = process.memory_info().rss
results["ram_before"] = round(ram_before / (1024 ** 2), 2)
results["ram_peak"] = round(ram_peak / (1024 ** 2), 2)
results["ram_after"] = round(ram_after / (1024 ** 2), 2)
results["ram_increase"] = round((ram_peak - ram_before) / (1024 ** 2), 2)
return results
if __name__ == "__main__":
res = run_isolation_forest(plot=True, table=True)
print(res)

View File

@ -0,0 +1,175 @@
# isolation_forest_custom.py
import json
import pandas as pd
import numpy as np
import psutil
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import RobustScaler
from sklearn.metrics import accuracy_score, precision_recall_fscore_support
import os
import gc
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEMP_DIR = os.path.join(BASE_DIR, "temp")
def _force_memory_cleanup():
gc.collect()
try:
import ctypes
ctypes.CDLL("libc.so.6").malloc_trim(0)
except Exception:
pass
def update_progress(value, progress_path="progress.json"):
with open(progress_path, "w") as f:
json.dump({"progress": value}, f)
def run_isolation_forest_custom(
csv_path=None,
config_path=None,
progress_path=None
):
if csv_path is None:
csv_path = os.path.join(TEMP_DIR, "upload.csv")
if config_path is None:
config_path = os.path.join(TEMP_DIR, "config.json")
if progress_path is None:
progress_path = os.path.join(BASE_DIR, "progress.json")
update_progress(1, progress_path)
process = psutil.Process()
ram_before = process.memory_info().rss
ram_peak = ram_before
update_progress(5, progress_path)
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
label_col = config["labeling"]["label_column"]
normal_value = config["labeling"]["normal_value"]
features = config["features"]["selected_columns"]
params = config.get("algorithm", {}).get("parameters", {})
needed_cols = list(dict.fromkeys(features + [label_col]))
df = pd.read_csv(csv_path, usecols=needed_cols)
update_progress(15, progress_path)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
col = df[label_col]
df["__label"] = col.apply(lambda x: 0 if x == normal_value else 1)
update_progress(25, progress_path)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
normal_count = int((df["__label"] == 0).sum())
attack_count = int((df["__label"] == 1).sum())
X = df[features].to_numpy(dtype=np.float32, copy=True)
y = df["__label"].to_numpy(dtype=np.int8, copy=True)
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X).astype(np.float32, copy=False)
contamination = min(y.mean() * 2.5, 0.49)
update_progress(45, progress_path)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
model = IsolationForest(
n_estimators=params.get("n_estimators", 600),
max_samples=params.get("max_samples", 0.3),
max_features=params.get("max_features", 0.7),
contamination=contamination,
random_state=params.get("random_state", 42),
n_jobs=1
)
model.fit(X_scaled)
update_progress(75, progress_path)
current_ram = process.memory_info().rss
ram_peak = max(ram_peak, current_ram)
preds = model.predict(X_scaled)
y_pred = np.where(preds == 1, 0, 1)
accuracy = accuracy_score(y, y_pred)
precision, recall, f1, _ = precision_recall_fscore_support(
y, y_pred, average=None, labels=[0, 1]
)
update_progress(85, progress_path)
results = {
"normal_count": float(normal_count),
"attack_count": float(attack_count),
"contamination": float(contamination),
"accuracy": float(accuracy),
"precision_normal": float(precision[0]),
"recall_normal": float(recall[0]),
"f1_normal": float(f1[0]),
"precision_attack": float(precision[1]),
"recall_attack": float(recall[1]),
"f1_attack": float(f1[1]),
}
anomaly_scores = -model.decision_function(X_scaled)
display_cols = list(dict.fromkeys(features + [label_col]))
top_df = df[display_cols].copy()
top_df["anomaly_score"] = anomaly_scores
top_df["predicted"] = y_pred
top_df["true_label"] = y
real_attacks = top_df[top_df["true_label"] == 1]
real_attacks_sorted = real_attacks.sort_values("anomaly_score", ascending=False)
top_real_attacks = real_attacks_sorted.head(5)
if len(top_real_attacks) > 0:
cols = [c for c in display_cols if c in top_real_attacks.columns] + ["anomaly_score"]
top_anomalies = (
top_real_attacks[cols]
.rename(columns={"anomaly_score": "score"})
.round(4)
.to_dict(orient="records")
)
else:
top_anomalies = []
results["top_anomalies"] = top_anomalies
del X, X_scaled, preds, y_pred, anomaly_scores
del top_df, real_attacks, real_attacks_sorted, top_real_attacks
del model, scaler, df, y, col
_force_memory_cleanup()
ram_after = process.memory_info().rss
results["ram_before"] = round(ram_before / (1024 ** 2), 2)
results["ram_peak"] = round(ram_peak / (1024 ** 2), 2)
results["ram_after"] = round(ram_after / (1024 ** 2), 2)
results["ram_increase"] = round((ram_peak - ram_before) / (1024 ** 2), 2)
return results
if __name__ == "__main__":
res = run_isolation_forest_custom()
print(json.dumps(res, indent=2))

1
Backend/progress.json Normal file
View File

@ -0,0 +1 @@
{"progress": 100}

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM python:3.11-slim-bullseye
WORKDIR /app
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
COPY requirements.txt .
RUN pip install --no-compile --no-cache-dir -r requirements.txt
COPY . /app
EXPOSE 5000
CMD ["python", "./Backend/app.py"]

View File

@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo demonstration!</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="bg-white text-gray-200 min-h-screen flex flex-col">
<div id="loadingOverlay"
class="hidden fixed inset-0 bg-black bg-opacity-80 flex flex-col justify-center items-center z-50">
<div class="animate-spin h-20 w-20 border-4 border-orange-500 border-t-transparent rounded-full"></div>
<p class="mt-6 text-2xl text-orange-500 font-semibold text-center">
Starting Isolation Forest...<br>This may take a while.
</p>
<div id="progressContainer" class="w-full max-w-lg mt-10 hidden flex flex-col items-center">
<div class="w-full bg-gray-700 rounded-full h-6 overflow-hidden shadow-lg">
<div id="progressBar"
class="bg-orange-500 h-6 transition-all duration-300 ease-out"
style="width: 0%;">
</div>
</div>
<p id="progressLabel" class="text-center text-orange-500 mt-3 text-xl font-semibold">
0%
</p>
</div>
</div>
<nav class="bg-orange-400 p-4 shadow-lg">
<div class="max-w-6xl mx-auto flex items-center justify-center">
<div class="max-w-6xl mx-auto flex items-center justify-center static">
<div class="w-40 absolute left-0">
<img src="/img/FEI_logo.svg" alt="FEI Logo">
</div>
<div class="w-40 absolute left-40">
<img src="/img/kemt_logo.png" alt="KEMT Logo">
</div>
<h1 class="text-2xl font-bold text-black">
Isolation Forest & Autoencoder Testing
</h1>
<button id="menuBtn"
class="absolute right-0 text-lg px-4 py-2 text-black hover:text-white transition">
Menu ▸
</button>
<div id="menuPanel"
class="hidden absolute right-0 top-14 w-48 bg-orange-500 border border-black rounded-xl shadow-xl
opacity-0 translate-y-3 transition-all duration-300">
<a href="/"
class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 rounded-t-xl transition">
Home
</a>
<a href="/upload"
class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 transition">
Upload Dataset
</a>
<a href="/default"
class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 rounded-b-xl transition">
CICIDS Demo
</a>
</div>
</div>
</div>
</nav>
<main class="flex-grow pb-10">
<div class="max-w-4xl mx-auto mt-40 p-6 bg-gray-600 rounded-xl shadow-xl">
<h2 class="text-3xl font-bold text-orange-500 mb-4 flex items-center justify-center">CICIDS Demo</h2>
<p class="text-lg leading-relaxed mb-6 text-center text-white">
Here, you can test the algoritmhs on
<span class="text-orange-500 font-semibold">CICIDS</span> dataset.
</p>
<h2 class="text-3xl font-bold text-orange-500 mb-4 flex items-center justify-center">
Choose your desired algorithm:
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-10">
<button id="btnIF"
class="algo-card bg-gray-700 p-6 rounded-xl shadow-xl transition-all duration-300 transform hover:-translate-y-1">
<h3 class="text-2xl font-semibold text-orange-500 mb-2">Isolation Forest</h3>
<p class="text-white">Detect anomalies using random isolation trees.</p>
</button>
<button id="btnAE"
class="algo-card bg-gray-700 p-6 rounded-xl shadow-xl transition-all duration-300 transform hover:-translate-y-1">
<h3 class="text-2xl font-semibold text-orange-500 mb-2">Autoencoder</h3>
<p class="text-white">Neural network reconstruction-based anomaly detection.</p>
</button>
</div>
<div class="w-full flex justify-center mt-10">
<button id="startBtn"
class="px-10 py-4 text-xl font-bold rounded-xl transition
bg-gray-700 text-orange-500 cursor-not-allowed">
Start
</button>
</div>
<div id="resultContainer" class="mt-12 hidden"></div>
</div>
</main>
<div id="footer" class="bg-orange-400 flex items-center justify-center h-10 mt-10">
<footer class="text-center text-black ">
<p>© 2025 Michal Utlak</p>
</footer>
</div>
<script src="/JS/menu.js"></script>
<script src="/JS/frontend.js"></script>
</body>
</html>

View File

@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Welcome to this testing demo website!</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-white text-gray-200 min-h-screen flex flex-col">
<nav class="bg-orange-400 p-6 shadow-lg">
<div class="max-w-6xl mx-auto flex items-center justify-center static">
<div class="w-40 absolute left-0">
<img src="/img/FEI_logo.svg" alt="FEI Logo">
</div>
<div class="w-40 absolute left-40">
<img src="/img/kemt_logo.png" alt="KEMT Logo">
</div>
<h1 class="text-2xl font-bold text-black">Isolation Forest & Autoencoder Testing</h1>
<button id="menuBtn" class="absolute right-0 text-lg px-4 py-2 text-black hover:text-white transition"> Menu ▸ </button>
<div id="menuPanel" class="hidden absolute right-0 top-14 w-48 bg-orange-500 border border-black rounded-xl shadow-xl opacity-0 translate-y-3 transition-all duration-300">
<a href="/" class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 rounded-t-xl transition"> Home </a>
<a href="/upload" class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 transition"> Upload Dataset </a>
<a href="/default" class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 rounded-b-xl transition"> CICIDS Demo </a>
</div>
</div>
</nav>
<main class="flex-grow pb-10">
<div class="max-w-4xl mx-auto mt-16 p-6 bg-gray-600 rounded-xl shadow-xl">
<h2 class="text-3xl font-bold text-orange-500 mb-4 flex items-center justify-center">Welcome!</h2>
<p class="text-lg leading-relaxed mb-6 text-center text-white">
This site serves as a demonstration of my Bachelor's Thesis project focused on
<span class="text-orange-500 font-semibold">Intrusion Detection Systems (IDS)</span>.
It showcases the testing and comparison of <span class="text-orange-500 font-semibold">Isolation Forest</span> and
<span class="text-orange-500 font-semibold">Autoencoder</span> algorithms on the popular
<span class="text-orange-500 font-semibold">CICIDS dataset</span>.
</p>
<h3 class="text-2xl font-bold text-orange-500 mt-10 mb-3 flex items-center justify-center">What is this project about?</h3>
<p class="text-lg leading-relaxed text-white">
The goal of this demo is to demonstrate how machine learning models can be used to:
</p>
<ul class="list-disc list-inside text-lg mt-4 space-y-2">
<li>Detect anomalies in network traffic</li>
<li>Identify potential cybersecurity threats</li>
<li>Process and analyze datasets in real time</li>
<li>Provide a modern and clear visual output</li>
</ul>
<h3 class="text-2xl font-bold text-orange-500 mt-10 mb-3 flex items-center justify-center">What algorithms are included?</h3>
<ul class="list-disc list-inside text-lg mt-4 space-y-2">
<li><strong class="text-orange-500">Isolation Forest</strong> anomaly detection based on random isolation</li>
<li><strong class="text-orange-500">Autoencoder</strong> neural network that reconstructs normal traffic and flags anomalies</li>
</ul>
<h3 class="text-2xl font-bold text-orange-500 mt-10 mb-3 flex items-center justify-center">What can you do here?</h3>
<ul class="list-disc list-inside text-lg mt-4 space-y-2">
<li>Upload your own dataset and test the algorithms</li>
<li>Run default tests on the CICIDS dataset</li>
</ul>
<p class="mt-10 text-center text-red-600 font-semibold text-3xl">
This demo is for educational and research purposes only!
</p>
</div>
</main>
<div id="footer" class="bg-orange-400 flex items-center justify-center h-10 mt-10">
<footer class="text-center text-black ">
<p>© 2025 Michal Utlak</p>
</footer>
</div>
<script src="/JS/menu.js"></script>
</body>
</html>

View File

@ -0,0 +1,264 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Upload your files in order to begin testing!</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body class="bg-white text-gray-200 min-h-screen flex flex-col">
<div id="loadingOverlay"
class="hidden fixed inset-0 bg-black bg-opacity-80 flex flex-col justify-center items-center z-50">
<div class="animate-spin h-20 w-20 border-4 border-orange-500 border-t-transparent rounded-full"></div>
<p class="mt-6 text-2xl text-orange-500 font-semibold text-center" id="loadingText"></p>
<div id="progressContainer" class="w-full max-w-lg mt-10 hidden flex flex-col items-center">
<div class="w-full bg-gray-700 rounded-full h-6 overflow-hidden shadow-lg">
<div id="progressBar"
class="bg-orange-500 h-6 transition-all duration-300 ease-out"
style="width: 0%;"></div>
</div>
<p id="progressLabel" class="text-center text-orange-500 mt-3 text-xl font-semibold">0%</p>
</div>
</div>
<nav class="bg-orange-400 p-4 shadow-lg">
<div class="max-w-6xl mx-auto flex items-center justify-center static">
<div class="w-40 absolute left-0">
<img src="/img/FEI_logo.svg" alt="FEI Logo">
</div>
<div class="w-40 absolute left-40">
<img src="/img/kemt_logo.png" alt="KEMT Logo">
</div>
<h1 class="text-2xl font-bold text-black">
Isolation Forest & Autoencoder Testing
</h1>
<button id="menuBtn"
class="absolute right-0 text-lg px-4 py-2 text-white hover:text-teal-300 transition">
Menu ▸
</button>
<div id="menuPanel"
class="hidden absolute right-0 top-14 w-48 bg-orange-500 border border-black rounded-xl shadow-xl
opacity-0 translate-y-3 transition-all duration-300">
<a href="/" class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 rounded-t-xl transition">Home</a>
<a href="/upload" class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 transition">Upload Dataset</a>
<a href="/default" class="block px-4 py-3 hover:bg-gray-600 hover:text-orange-500 rounded-b-xl transition">CICIDS Demo</a>
</div>
</div>
</nav>
<main class="flex-grow pb-10">
<div class="max-w-4xl mx-auto mt-16 p-6 bg-gray-600 rounded-xl shadow-xl">
<section id="step-1">
<h2 class="text-3xl font-bold text-orange-500 mb-4 flex items-center justify-center">
Upload your custom dataset here!
</h2>
<p class="text-lg leading-relaxed mb-6 text-center">
Here, you can upload your <span class="text-orange-500 font-semibold">own</span> dataset and test it out on either
<span class="text-orange-500 font-semibold">Isolation Forest</span> or
<span class="text-orange-500 font-semibold">Autoencoder</span>.
</p>
<p class="text-lg leading-relaxed mb-6 text-center">
Please note, you can only use .CSV format when uploading.
</p>
<form class="w-full flex justify-center">
<label for="file_input"
class="group relative cursor-pointer w-full max-w-md border-2 border-dashed border-orange-500/50
rounded-2xl p-8 text-center bg-gradient-to-br from-gray-900 to-gray-800 transition-all duration-300
hover:border-orange-300 hover:shadow-2xl hover:shadow-orange-500/25">
<div class="flex flex-col items-center gap-4">
<svg xmlns="http://www.w3.org/2000/svg"
class="h-12 w-12 text-orange-500 transition-transform duration-300 group-hover:scale-110"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M12 16v-8m0 0l-3 3m3-3l3 3M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2" />
</svg>
<input
id="file_input"
type="file"
name="filename"
accept=".csv"
class="block text-sm text-gray-300
file:mr-4 file:py-2 file:px-4
file:rounded-lg file:border-0
file:bg-orange-500 file:text-gray-900
hover:file:bg-orange-400 transition" />
<p id="file_hint" class="text-sm text-gray-400">Upload a CSV file to continue.</p>
</div>
</label>
</form>
</section>
<section id="step-2" class="hidden mt-12">
<h3 class="text-2xl font-bold text-orange-500 mb-4 text-center">
Select column that represents if data are normal or anomaly
</h3>
<div class="flex flex-col items-center gap-4">
<select id="label_select" class="bg-gray-900 border border-gray-600 rounded-lg px-4 py-2 w-72">
<option value="">-- select label column --</option>
</select>
<p id="label_error" class="text-red-400 text-sm hidden">Please select a label column.</p>
<button id="btn_step2_next"
class="px-6 py-2 rounded-lg bg-orange-500 text-white font-semibold hover:bg-orange-400 transition">
Next
</button>
</div>
</section>
<section id="step-3" class="hidden mt-12">
<h3 class="text-2xl font-bold text-orange-500 mb-2 text-center">
Select NORMAL label values
</h3>
<p class="text-white text-sm text-center mb-6">
Choose which values represent <span class="text-orange-500 font-semibold">normal</span> traffic/data.
</p>
<div id="normal_values" class="max-w-md mx-auto space-y-2"></div>
<p id="normal_error" class="text-red-400 text-sm text-center hidden mt-3">
Select at least one normal value.
</p>
<div class="flex justify-center mt-6">
<button id="btn_step3_next"
class="px-6 py-2 rounded-lg bg-orange-500 text-gray-900 font-semibold hover:bg-orange-400 transition">
Next
</button>
</div>
</section>
<section id="step-4" class="hidden mt-12">
<h3 class="text-2xl font-bold text-orange-500 mb-2 text-center">
Select numeric feature columns
</h3>
<p class="text-white text-sm text-center mb-4">
Pick up to <span class="text-orange-500 font-semibold">5</span> numeric columns for testing.
</p>
<div class="flex justify-center mb-3">
<span id="feature_counter"
class="text-sm text-gray-300 bg-gray-900/50 border border-gray-700 rounded-full px-4 py-1">
0 / 5 selected
</span>
</div>
<div id="feature_list" class="max-w-md mx-auto space-y-2"></div>
<p id="feature_error" class="text-red-400 text-sm text-center hidden mt-3">
Select at least 1 and at most 5 numeric columns.
</p>
<div class="flex justify-center mt-6">
<button id="btn_run"
class="px-6 py-2 rounded-lg bg-orange-500 text-white font-semibold hover:bg-orange-400 transition">
Run
</button>
</div>
</section>
<section id="step-5" class="hidden mt-16">
<h2 class="text-3xl font-bold text-orange-500 mb-4 flex items-center justify-center">
Choose your desired algorithm:
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-10">
<button id="btnIF"
class="bg-gray-700 p-6 rounded-xl shadow-xl hover:-translate-y-1 transition">
<h3 class="text-2xl font-semibold text-orange-500 mb-2">Isolation Forest</h3>
<p class="text-white">Detect anomalies using random isolation trees.</p>
</button>
<button id="btnAE"
class="bg-gray-700 p-6 rounded-xl shadow-xl hover:-translate-y-1 transition">
<h3 class="text-2xl font-semibold text-orange-500 mb-2">Autoencoder</h3>
<p class="text-white">Neural network reconstruction-based anomaly detection.</p>
</button>
</div>
<div class="w-full flex justify-center mt-10">
<button id="startBtn"
class="px-10 py-4 text-xl font-bold rounded-xl bg-gray-700 text-white cursor-not-allowed">
Start
</button>
</div>
<div id="resultContainer" class="mt-12 hidden"></div>
</section>
</div>
</main>
<footer class="bg-orange-400 flex items-center justify-center h-10 w-full mt-auto">
<p class="text-black">© 2025 Michal Utlak</p>
</footer>
<script src="/JS/menu.js"></script>
<script src="/JS/upload.js"></script>
<script src="/JS/upload_choosing_fetch.js"></script>
<script>
const uploadField = document.getElementById("file_input");
uploadField.onchange = function() {
const MAX_SIZE = 50 * 1024 * 1024;
if(this.files[0].size > MAX_SIZE) {
showErrorPopup("File is too big! Maximum size is 50MB.");
this.value = "";
}
};
function showErrorPopup(message) {
const popup = document.createElement("div");
popup.innerHTML = `
<div class="fixed inset-0 flex justify-center items-center bg-black bg-opacity-70 z-50">
<div class="bg-red-600 p-6 rounded-xl shadow-xl text-center max-w-md">
<h2 class="text-2xl font-bold text-white mb-4">Error</h2>
<p class="text-white mb-6">${message}</p>
<button id="closeError"
class="bg-white text-red-600 px-6 py-2 rounded-lg font-semibold hover:bg-gray-200 transition">
Close
</button>
</div>
</div>
`;
document.body.appendChild(popup);
document.getElementById("closeError").onclick = () => popup.remove();
}
</script>
</body>
</html>

467
Frontend/JS/frontend.js Normal file
View File

@ -0,0 +1,467 @@
const btnIF = document.getElementById("btnIF");
const btnAE = document.getElementById("btnAE");
const startBtn = document.getElementById("startBtn");
let chosenAlgorithm = null;
let chosenAlgorithmName = "";
let startTime = null;
let lastResults = null;
let progressInterval = null;
let backendErrorShown = false;
const JOB_STORAGE_KEY = "default_active_job";
function selectAlgorithm(selected, other) {
selected.classList.add("scale-105", "border-2", "border-black");
selected.classList.remove("opacity-50");
other.classList.remove("scale-105", "border-2", "border-black");
other.classList.add("opacity-50");
}
function enableStartButton() {
startBtn.classList.remove("bg-gray-700", "text-gray-300", "cursor-not-allowed");
startBtn.classList.add("bg-orange-500", "hover:bg-orange-400", "text-white", "cursor-pointer");
}
btnIF.addEventListener("click", () => {
selectAlgorithm(btnIF, btnAE);
chosenAlgorithm = "isolation_forest";
chosenAlgorithmName = "Isolation Forest";
enableStartButton();
});
btnAE.addEventListener("click", () => {
selectAlgorithm(btnAE, btnIF);
chosenAlgorithm = "autoencoder";
chosenAlgorithmName = "Autoencoder";
enableStartButton();
});
function getBackendJobName() {
return chosenAlgorithm === "isolation_forest" ? "iforest" : "autoencoder";
}
function getResultURL() {
return chosenAlgorithm === "isolation_forest"
? "/get-isolation-forest-result"
: "/get-autoencoder-result";
}
function saveActiveJob() {
sessionStorage.setItem(JOB_STORAGE_KEY, JSON.stringify({
algorithm: chosenAlgorithm,
algorithmName: chosenAlgorithmName,
backendJob: getBackendJobName(),
startedAt: Date.now()
}));
}
function loadActiveJob() {
const raw = sessionStorage.getItem(JOB_STORAGE_KEY);
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
sessionStorage.removeItem(JOB_STORAGE_KEY);
return null;
}
}
function clearActiveJob() {
sessionStorage.removeItem(JOB_STORAGE_KEY);
}
function applySavedAlgorithm(saved) {
chosenAlgorithm = saved.algorithm;
chosenAlgorithmName = saved.algorithmName;
startTime = saved.startedAt;
if (chosenAlgorithm === "isolation_forest") {
selectAlgorithm(btnIF, btnAE);
} else if (chosenAlgorithm === "autoencoder") {
selectAlgorithm(btnAE, btnIF);
}
enableStartButton();
}
function updateProgressUI(progress) {
document.getElementById("progressContainer").classList.remove("hidden");
document.getElementById("progressBar").style.width = progress + "%";
document.getElementById("progressLabel").textContent = progress + "%";
}
function showLoading(customText = null) {
document.getElementById("loadingOverlay").classList.remove("hidden");
document.getElementById("progressBar").style.width = "0%";
document.getElementById("progressLabel").textContent = "0%";
document.getElementById("progressContainer").classList.add("hidden");
const loadingText = document.querySelector("#loadingOverlay p");
if (loadingText) {
loadingText.innerHTML = customText ?? `Starting ${chosenAlgorithmName}...<br>This may take a while.`;
}
}
function hideLoading() {
document.getElementById("loadingOverlay").classList.add("hidden");
}
function showErrorPopup(message) {
const popup = document.createElement("div");
popup.innerHTML = `
<div class="fixed inset-0 flex justify-center items-center bg-black bg-opacity-70 z-50">
<div class="bg-red-600 p-6 rounded-xl shadow-xl text-center max-w-md">
<h2 class="text-2xl font-bold text-white mb-4">Error</h2>
<p class="text-white mb-6">${message}</p>
<button id="closeError"
class="bg-white text-red-600 px-6 py-2 rounded-lg font-semibold hover:bg-gray-200 transition">
Close
</button>
</div>
</div>
`;
document.body.appendChild(popup);
document.getElementById("closeError").onclick = () => popup.remove();
}
function fetchFinalResults(retryCount = 0) {
fetch(getResultURL())
.then(res => res.json())
.then(json => {
if (!json.ready) {
if (retryCount < 10) {
setTimeout(() => fetchFinalResults(retryCount + 1), 1000);
return;
}
hideLoading();
clearActiveJob();
showErrorPopup("Final results are not ready.");
return;
}
hideLoading();
clearActiveJob();
lastResults = json.data;
if (json.data?.error) {
showErrorPopup(`Algorithm failed.<br>${json.data.message}`);
return;
}
displayResults(json.data);
})
.catch(err => {
hideLoading();
clearActiveJob();
showErrorPopup("Error fetching final results.");
});
}
function startProgressPolling() {
if (progressInterval) {
clearInterval(progressInterval);
}
document.getElementById("progressContainer").classList.remove("hidden");
progressInterval = setInterval(async () => {
try {
const res = await fetch("/get-status");
if (!res.ok) {
throw new Error("Backend status fetch failed");
}
const data = await res.json();
updateProgressUI(data.progress || 0);
if (!data.running) {
clearInterval(progressInterval);
progressInterval = null;
fetchFinalResults();
}
} catch (err) {
clearInterval(progressInterval);
progressInterval = null;
hideLoading();
clearActiveJob();
if (!backendErrorShown) {
backendErrorShown = true;
showErrorPopup("Connection to backend was lost.<br>Backend may have stopped.");
}
}
}, 1500);
}
async function restoreRunningJob() {
const saved = loadActiveJob();
if (!saved) return;
try {
const res = await fetch("/get-status");
const status = await res.json();
if (status.current_job !== saved.backendJob) {
clearActiveJob();
return;
}
applySavedAlgorithm(saved);
showLoading(`Resuming ${chosenAlgorithmName}...<br>This may take a while.`);
updateProgressUI(status.progress || 0);
if (status.running) {
startProgressPolling();
} else {
fetchFinalResults();
}
} catch (err) {
hideLoading();
clearActiveJob();
if (!backendErrorShown) {
backendErrorShown = true;
showErrorPopup("Connection to backend was lost.<br>Please start the backend and try again.");
}
}
}
startBtn.addEventListener("click", () => {
if (!chosenAlgorithm) return;
showLoading();
backendErrorShown = false;
saveActiveJob();
let url =
chosenAlgorithm === "isolation_forest"
? "/start-iforest"
: "/start-autoencoder";
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000);
fetch(url, { method: "POST", signal: controller.signal })
.then(async res => {
clearTimeout(timeout);
if (res.status === 409) {
hideLoading();
clearActiveJob();
showErrorPopup("Another algorithm is already running.<br>Please wait until it finishes.");
return;
}
if (!res.ok) {
throw new Error("Server error");
}
const saved = loadActiveJob();
startTime = saved?.startedAt ?? Date.now();
startProgressPolling();
})
.catch(err => {
hideLoading();
clearActiveJob();
console.error(err);
showErrorPopup("Backend server is not running.<br>Start app.py and try again.");
});
});
window.addEventListener("load", restoreRunningJob);
function displayResults(data) {
const container = document.getElementById("resultContainer");
container.classList.remove("hidden");
const runtime = ((Date.now() - startTime) / 1000).toFixed(2);
// Dynamicky vytvoríme tabuľku pre Top 5 anomálie (ak existujú)
let topAnomaliesHTML = '';
if (data.top_anomalies && data.top_anomalies.length > 0) {
const headers = Object.keys(data.top_anomalies[0]);
topAnomaliesHTML = `
<div class="mt-12">
<h3 class="text-2xl font-bold text-orange-500 mb-6 text-center">
Top 5 detected anomalies
</h3>
<div class="overflow-x-auto">
<table class="min-w-full bg-gray-800 rounded-xl shadow-xl">
<thead class="bg-gray-700">
<tr>
${headers.map(h => `<th class="px-4 py-3 text-orange-500">${h}</th>`).join('')}
</tr>
</thead>
<tbody>
${data.top_anomalies.map(row => `
<tr class="border-b border-gray-700 hover:bg-gray-700/50 transition">
${headers.map(key => {
const val = row[key];
return `<td class="px-4 py-3 text-center">${typeof val === 'number' ? val.toFixed(4) : val}</td>`;
}).join('')}
</tr>`).join('')}
</tbody>
</table>
</div>
</div>`;
}
container.innerHTML = `
<h2 class="text-3xl font-bold text-orange-500 mb-8 text-center">
Results
</h2>
<!-- Metriky tabuľka -->
<div class="overflow-x-auto mb-10">
<table class="min-w-full bg-gray-800 rounded-xl shadow-xl text-center">
<thead class="bg-gray-700">
<tr>
<th class="px-6 py-3 text-orange-500">Class</th>
<th class="px-6 py-3 text-orange-500">Precision</th>
<th class="px-6 py-3 text-orange-500">Recall</th>
<th class="px-6 py-3 text-orange-500">F1-Score</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-gray-700">
<td class="py-4 px-6 font-semibold">Normal traffic</td>
<td class="py-4 px-6">${data.precision_normal.toFixed(4)}</td>
<td class="py-4 px-6">${data.recall_normal.toFixed(4)}</td>
<td class="py-4 px-6">${data.f1_normal.toFixed(4)}</td>
</tr>
<tr class="border-b border-gray-700">
<td class="py-4 px-6 font-semibold">Anomaly</td>
<td class="py-4 px-6">${data.precision_attack.toFixed(4)}</td>
<td class="py-4 px-6">${data.recall_attack.toFixed(4)}</td>
<td class="py-4 px-6">${data.f1_attack.toFixed(4)}</td>
</tr>
<tr class="bg-gray-700 text-white font-bold">
<td class="py-4 px-6">Overall accuracy</td>
<td class="py-4 px-6">-</td>
<td class="py-4 px-6">-</td>
<td class="py-4 px-6">${data.accuracy.toFixed(4)}</td>
</tr>
</tbody>
</table>
</div>
<!-- Distribúcia + Systémové info -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 mb-10">
<!-- Pie Chart -->
<div class="flex flex-col items-center">
<h3 class="text-2xl text-orange-500 font-semibold mb-4">Class distribution</h3>
<canvas id="pieChart" class="w-80 h-80"></canvas>
</div>
<!-- RAM a počty -->
<div class="bg-gray-800/60 rounded-xl p-8 border border-gray-700">
<h3 class="text-2xl text-orange-500 font-semibold mb-6">Performance</h3>
<div class="space-y-4 text-lg">
<div class="flex justify-between"><span class="text-gray-400">Normal traffic:</span> <span class="font-bold text-white">${Math.round(data.normal_count)}</span></div>
<div class="flex justify-between"><span class="text-gray-400">Anomalies:</span> <span class="font-bold text-white">${Math.round(data.attack_count)}</span></div>
<div class="flex justify-between"><span class="text-gray-400">Runtime:</span> <span class="font-bold text-white">${runtime} s</span></div>
<div class="flex justify-between"><span class="text-gray-400">RAM before:</span> <span class="font-bold text-white">${data.ram_before} MB</span></div>
<div class="flex justify-between"><span class="text-gray-400">RAM peak:</span> <span class="font-bold text-white">${data.ram_peak} MB</span></div>
<div class="flex justify-between"><span class="text-gray-400">RAM after:</span> <span class="font-bold text-white">${data.ram_after} MB</span></div>
<div class="flex justify-between"><span class="text-red-400 font-semibold">RAM increase:</span> <span class="font-bold text-red-300">+${data.ram_increase} MB</span></div>
</div>
</div>
</div>
${topAnomaliesHTML}
<!-- Download tlačidlá -->
<div class="w-full flex justify-center gap-10 mt-12">
<button id="downloadPDF"
class="px-10 py-5 bg-orange-500 hover:bg-orange-400 text-white text-xl font-bold rounded-xl transition shadow-xl cursor-pointer transform hover:scale-105">
Download PDF
</button>
<button id="downloadJSON"
class="px-10 py-5 bg-yellow-500 hover:bg-yellow-400 text-white text-xl font-bold rounded-xl transition shadow-xl cursor-pointer transform hover:scale-105">
Download JSON
</button>
</div>
`;
loadPieChart(data);
document.getElementById("downloadPDF").addEventListener("click", () => {
downloadPDF(data, runtime);
});
document.getElementById("downloadJSON").addEventListener("click", () => {
downloadJSON(data, runtime);
});
}
function downloadPDF(results, runtime) {
fetch("/download-pdf", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
algorithm: chosenAlgorithmName,
results: results,
runtime: runtime
})
})
.then(response => response.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "report.pdf";
link.click();
});
}
function downloadJSON(results, runtime) {
fetch("/download-json", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
algorithm: chosenAlgorithmName,
results: results,
runtime: runtime
})
})
.then(res => res.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "report.json";
link.click();
});
}
function loadPieChart(data) {
const ctx = document.getElementById("pieChart");
new Chart(ctx, {
type: "pie",
data: {
labels: ["Normal Traffic", "Attacks"],
datasets: [{
data: [data.normal_count || 0, data.attack_count || 0],
backgroundColor: ["#14b8a6", "#dc2626"],
}]
},
options: {
plugins: {
legend: {
labels: {
color: "#ffffff"
}
}
}
}
});
}

20
Frontend/JS/menu.js Normal file
View File

@ -0,0 +1,20 @@
const btn = document.getElementById("menuBtn");
const panel = document.getElementById("menuPanel");
btn.addEventListener("click", () => {
if (panel.classList.contains("hidden")) {
panel.classList.remove("hidden");
setTimeout(() => {
panel.classList.remove("opacity-0", "translate-y-3");
panel.classList.add("opacity-100", "translate-y-0");
btn.innerText = "Menu ▾";
}, 10);
} else {
panel.classList.add("opacity-0", "translate-y-3");
panel.classList.remove("opacity-100", "translate-y-0");
btn.innerText = "Menu ▸";
setTimeout(() => {
panel.classList.add("hidden");
}, 250);
}
});

271
Frontend/JS/upload.js Normal file
View File

@ -0,0 +1,271 @@
function show(el) { el.classList.remove("hidden"); }
function hide(el) { el.classList.add("hidden"); }
function escapeHtml(str) {
return String(str)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function splitCsvLine(line) {
const out = [];
let cur = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (ch === '"') {
if (inQuotes && line[i + 1] === '"') {
cur += '"';
i++;
} else {
inQuotes = !inQuotes;
}
} else if (ch === "," && !inQuotes) {
out.push(cur);
cur = "";
} else {
cur += ch;
}
}
out.push(cur);
return out.map(v => v.trim());
}
const fileInput = document.getElementById("file_input");
const step2 = document.getElementById("step-2");
const step3 = document.getElementById("step-3");
const step4 = document.getElementById("step-4");
const step5 = document.getElementById("step-5");
const labelSelect = document.getElementById("label_select");
const labelError = document.getElementById("label_error");
const normalBox = document.getElementById("normal_values");
const normalError = document.getElementById("normal_error");
const featureList = document.getElementById("feature_list");
const featureCounter = document.getElementById("feature_counter");
const featureError = document.getElementById("feature_error");
const btnStep2Next = document.getElementById("btn_step2_next");
const btnStep3Next = document.getElementById("btn_step3_next");
const btnRun = document.getElementById("btn_run");
const MAX_FEATURES = 5;
let headers = [];
let rows = [];
let labelIndex = -1;
let numericColumnIdx = [];
fileInput.addEventListener("change", async () => {
const formData = new FormData();
formData.append("file", fileInput.files[0]);
await fetch("/upload-dataset", {
method: "POST",
body: formData
});
if (!fileInput.files.length) return;
const file = fileInput.files[0];
const text = await file.text();
const lines = text.split(/\r?\n/).filter(l => l.trim() !== "");
if (lines.length < 2) return;
headers = splitCsvLine(lines[0]);
rows = lines.slice(1).map(splitCsvLine);
labelSelect.innerHTML = `<option value="">-- select label column --</option>`;
headers.forEach((h, idx) => {
const opt = document.createElement("option");
opt.value = idx;
opt.textContent = h;
labelSelect.appendChild(opt);
});
numericColumnIdx = detectNumericColumns(headers, rows, 30);
show(step2);
hide(step3);
hide(step4);
hide(step5);
hide(labelError);
});
function detectNumericColumns(headers, rows, sampleCount) {
const result = [];
const samples = Math.min(sampleCount, rows.length);
for (let c = 0; c < headers.length; c++) {
let valid = 0;
let total = 0;
for (let r = 0; r < samples; r++) {
const v = rows[r][c];
if (v === undefined || v === "") continue;
total++;
if (!isNaN(Number(v))) valid++;
}
if (total > 0 && valid / total >= 0.9) {
result.push(c);
}
}
return result;
}
btnStep2Next.addEventListener("click", () => {
if (labelSelect.value === "") {
show(labelError);
return;
}
hide(labelError);
labelIndex = Number(labelSelect.value);
const uniques = new Set();
rows.forEach(r => {
const v = r[labelIndex];
if (v !== undefined && v !== "") uniques.add(v.trim());
});
normalBox.innerHTML = "";
uniques.forEach(val => {
const label = document.createElement("label");
label.className =
"flex items-center gap-3 bg-gray-900/40 border border-gray-700 rounded-lg px-4 py-2";
label.innerHTML = `
<input type="checkbox" value="${escapeHtml(val)}" class="accent-orange-500">
<span>${escapeHtml(val)}</span>
`;
normalBox.appendChild(label);
});
normalBox.querySelectorAll("input").forEach(cb => {
cb.addEventListener("change", () => {
if (cb.checked) {
normalBox.querySelectorAll("input").forEach(o => {
if (o !== cb) o.checked = false;
});
}
});
});
show(step3);
hide(step4);
hide(step5);
hide(normalError);
});
btnStep3Next.addEventListener("click", () => {
const checked = normalBox.querySelectorAll("input:checked");
if (checked.length !== 1) {
normalError.textContent = "Select exactly one NORMAL value.";
show(normalError);
return;
}
hide(normalError);
featureList.innerHTML = "";
numericColumnIdx
.filter(idx => idx !== labelIndex)
.forEach(idx => {
const label = document.createElement("label");
label.className =
"flex items-center justify-between bg-gray-900/40 border border-gray-700 rounded-lg px-4 py-2";
label.innerHTML = `
<div class="flex items-center gap-3">
<input type="checkbox" data-col="${idx}" class="accent-orange-500">
<span>${escapeHtml(headers[idx])}</span>
</div>
<span class="text-xs text-gray-500">numeric</span>
`;
featureList.appendChild(label);
});
featureList.querySelectorAll("input").forEach(cb =>
cb.addEventListener("change", updateCounter)
);
updateCounter();
show(step4);
hide(featureError);
});
function updateCounter() {
const all = featureList.querySelectorAll("input");
const selected = featureList.querySelectorAll("input:checked").length;
featureCounter.textContent = `${selected} / ${MAX_FEATURES} selected`;
all.forEach(cb => {
cb.disabled = selected >= MAX_FEATURES && !cb.checked;
cb.classList.toggle("opacity-60", cb.disabled);
});
}
btnRun.addEventListener("click", async () => {
const labelColumnName = headers[labelIndex];
let normalValue = normalBox.querySelector("input:checked").value;
if (!isNaN(normalValue)) {
normalValue = Number(normalValue);
}
const selectedFeatures = Array.from(
featureList.querySelectorAll("input:checked")
).map(cb => headers[cb.dataset.col]);
const config = {
dataset: {
file_path: "temp/upload.csv",
dataset_name: "custom_user_dataset"
},
labeling: {
label_column: labelColumnName,
normal_value: normalValue,
normal_label: 0,
anomaly_label: 1
},
features: {
selected_columns: selectedFeatures,
expected_feature_count: selectedFeatures.length
},
algorithm: {
name: "isolation_forest"
}
};
await fetch("/save-config", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(config)
});
const selected = featureList.querySelectorAll("input:checked").length;
if (selected !== MAX_FEATURES) {
featureError.textContent = `You must select exactly ${MAX_FEATURES} numeric features.`;
show(featureError);
return;
}
hide(featureError);
show(step5);
});

View File

@ -0,0 +1,516 @@
const btnIF = document.getElementById("btnIF");
const btnAE = document.getElementById("btnAE");
const startBtn = document.getElementById("startBtn");
let chosenAlgorithm = null;
let chosenAlgorithmName = null;
let startTime = null;
let lastResults = null;
let progressInterval = null;
let backendErrorShown = false;
const JOB_STORAGE_KEY = "custom_active_job";
function selectAlgorithm(selected, other) {
selected.classList.add("scale-105", "border-2", "border-black");
selected.classList.remove("opacity-50");
other.classList.remove("scale-105", "border-2", "border-black");
other.classList.add("opacity-50");
}
function enableStartButton() {
startBtn.classList.remove("bg-gray-600", "text-gray-300", "cursor-not-allowed");
startBtn.classList.add("bg-orange-500", "hover:bg-orange-400", "text-white", "cursor-pointer");
}
btnIF.addEventListener("click", () => {
selectAlgorithm(btnIF, btnAE);
chosenAlgorithm = "iforest_custom";
chosenAlgorithmName = "Isolation Forest (Custom)";
enableStartButton();
});
btnAE.addEventListener("click", () => {
selectAlgorithm(btnAE, btnIF);
chosenAlgorithm = "autoencoder_custom";
chosenAlgorithmName = "Autoencoder (Custom)";
enableStartButton();
});
function getBackendJobName() {
return chosenAlgorithm;
}
function getResultURL() {
if (chosenAlgorithm === "iforest_custom") {
return "/get-isolation-forest-result-custom";
}
return "/get-autoencoder-result-custom";
}
function saveActiveJob() {
sessionStorage.setItem(JOB_STORAGE_KEY, JSON.stringify({
algorithm: chosenAlgorithm,
algorithmName: chosenAlgorithmName,
backendJob: getBackendJobName(),
startedAt: Date.now()
}));
}
function loadActiveJob() {
const raw = sessionStorage.getItem(JOB_STORAGE_KEY);
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
sessionStorage.removeItem(JOB_STORAGE_KEY);
return null;
}
}
function clearActiveJob() {
sessionStorage.removeItem(JOB_STORAGE_KEY);
}
function applySavedAlgorithm(saved) {
chosenAlgorithm = saved.algorithm;
chosenAlgorithmName = saved.algorithmName;
startTime = saved.startedAt;
if (chosenAlgorithm === "iforest_custom") {
selectAlgorithm(btnIF, btnAE);
} else if (chosenAlgorithm === "autoencoder_custom") {
selectAlgorithm(btnAE, btnIF);
}
enableStartButton();
}
function restoreCustomRunLayout() {
const step1 = document.getElementById("step-1");
const step2 = document.getElementById("step-2");
const step3 = document.getElementById("step-3");
const step4 = document.getElementById("step-4");
const step5 = document.getElementById("step-5");
if (step1) step1.classList.add("hidden");
if (step2) step2.classList.add("hidden");
if (step3) step3.classList.add("hidden");
if (step4) step4.classList.add("hidden");
if (step5) step5.classList.remove("hidden");
}
function updateProgressUI(progress) {
document.getElementById("progressContainer").classList.remove("hidden");
document.getElementById("progressBar").style.width = progress + "%";
document.getElementById("progressLabel").textContent = progress + "%";
}
function showLoading(customText = null) {
document.getElementById("loadingOverlay").classList.remove("hidden");
document.getElementById("progressBar").style.width = "0%";
document.getElementById("progressLabel").textContent = "0%";
document.getElementById("progressContainer").classList.add("hidden");
const loadingText = document.getElementById("loadingText");
if (loadingText) {
loadingText.innerHTML = customText ?? `Starting ${chosenAlgorithmName}...<br>This may take a while.`;
}
}
function hideLoading() {
document.getElementById("loadingOverlay").classList.add("hidden");
}
function showErrorPopup(message) {
const popup = document.createElement("div");
popup.innerHTML = `
<div class="fixed inset-0 flex justify-center items-center bg-black bg-opacity-70 z-50">
<div class="bg-red-600 p-6 rounded-xl shadow-xl text-center max-w-md">
<h2 class="text-2xl font-bold text-white mb-4">Error</h2>
<p class="text-white mb-6">${message}</p>
<button id="closeError"
class="bg-white text-red-600 px-6 py-2 rounded-lg font-semibold hover:bg-gray-200 transition">
Close
</button>
</div>
</div>
`;
document.body.appendChild(popup);
document.getElementById("closeError").onclick = () => popup.remove();
}
const originalFetch = window.fetch.bind(window);
window.fetch = async function (...args) {
const res = await originalFetch(...args);
let url = "";
const req = args[0];
if (typeof req === "string") {
url = req;
} else if (req && typeof req.url === "string") {
url = req.url;
}
if (res.status === 409) {
let message = "Another operation is already running.";
try {
const data = await res.clone().json();
if (data?.error) {
message = data.error;
} else if (data?.message) {
message = data.message;
}
} catch (_) {}
if (url.includes("/upload-dataset")) {
showErrorPopup(message || "Cannot upload a new dataset while an algorithm is running.");
} else if (url.includes("/save-config")) {
showErrorPopup(message || "Cannot save config while an algorithm is running.");
}
}
return res;
};
function fetchFinalResults(retryCount = 0) {
fetch(getResultURL())
.then(res => res.json())
.then(json => {
if (!json.ready) {
if (retryCount < 10) {
setTimeout(() => fetchFinalResults(retryCount + 1), 1000);
return;
}
hideLoading();
clearActiveJob();
showErrorPopup("Final results are not ready.");
return;
}
hideLoading();
clearActiveJob();
restoreCustomRunLayout();
lastResults = json.data;
if (json.data?.error) {
showErrorPopup(`Algorithm failed.<br>${json.data.message}`);
return;
}
displayResults(json.data);
})
.catch(() => {
hideLoading();
clearActiveJob();
showErrorPopup("Error fetching final results.");
});
}
function startProgressPolling() {
if (progressInterval) {
clearInterval(progressInterval);
}
document.getElementById("progressContainer").classList.remove("hidden");
progressInterval = setInterval(async () => {
try {
const res = await fetch("/get-status");
if (!res.ok) {
throw new Error("Backend status fetch failed");
}
const data = await res.json();
updateProgressUI(data.progress || 0);
if (!data.running) {
clearInterval(progressInterval);
progressInterval = null;
fetchFinalResults();
}
} catch (err) {
clearInterval(progressInterval);
progressInterval = null;
hideLoading();
clearActiveJob();
if (!backendErrorShown) {
backendErrorShown = true;
showErrorPopup("Connection to backend was lost.<br>Backend may have stopped.");
}
}
}, 1500);
}
async function restoreRunningJob() {
const saved = loadActiveJob();
if (!saved) return;
try {
const res = await fetch("/get-status");
const status = await res.json();
if (status.current_job !== saved.backendJob) {
clearActiveJob();
return;
}
applySavedAlgorithm(saved);
restoreCustomRunLayout();
showLoading(`Resuming ${chosenAlgorithmName}...<br>This may take a while.`);
updateProgressUI(status.progress || 0);
if (status.running) {
startProgressPolling();
} else {
fetchFinalResults();
}
} catch (err) {
hideLoading();
clearActiveJob();
if (!backendErrorShown) {
backendErrorShown = true;
showErrorPopup("Connection to backend was lost.<br>Please start the backend and try again.");
}
}
}
startBtn.addEventListener("click", () => {
if (!chosenAlgorithm) return;
showLoading();
backendErrorShown = false;
saveActiveJob();
let url;
if (chosenAlgorithm === "iforest_custom") {
url = "/start-iforest-custom";
} else if (chosenAlgorithm === "autoencoder_custom") {
url = "/start-autoencoder-custom";
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 60000);
fetch(url, { method: "POST", signal: controller.signal })
.then(async res => {
clearTimeout(timeout);
if (res.status === 409) {
hideLoading();
clearActiveJob();
showErrorPopup("Another algorithm is already running.<br>Please wait until it finishes.");
return;
}
if (!res.ok) {
throw new Error("Server error");
}
const saved = loadActiveJob();
startTime = saved?.startedAt ?? Date.now();
startProgressPolling();
})
.catch(err => {
hideLoading();
clearActiveJob();
console.error(err);
showErrorPopup("Backend server is not running.<br>Start app.py and try again.");
});
});
window.addEventListener("load", restoreRunningJob);
function displayResults(data) {
const container = document.getElementById("resultContainer");
container.classList.remove("hidden");
const runtime = ((Date.now() - startTime) / 1000).toFixed(2);
let topAnomaliesHTML = '';
if (data.top_anomalies && data.top_anomalies.length > 0) {
const headers = Object.keys(data.top_anomalies[0]);
topAnomaliesHTML = `
<div class="mt-12">
<h3 class="text-2xl font-bold text-orange-500 mb-6 text-center">
Top 5 detected anomalies
</h3>
<div class="overflow-x-auto">
<table class="min-w-full bg-gray-800 rounded-xl shadow-xl">
<thead class="bg-gray-700">
<tr>
${headers.map(h => `<th class="px-4 py-3 text-orange-500">${h}</th>`).join('')}
</tr>
</thead>
<tbody>
${data.top_anomalies.map(row => `
<tr class="border-b border-gray-700 hover:bg-gray-700/50 transition">
${headers.map(key => {
const val = row[key];
return `<td class="px-4 py-3 text-center">${typeof val === 'number' ? val.toFixed(4) : val}</td>`;
}).join('')}
</tr>`).join('')}
</tbody>
</table>
</div>
</div>`;
}
container.innerHTML = `
<h2 class="text-3xl font-bold text-orange-500 mb-8 text-center">
Results
</h2>
<div class="overflow-x-auto mb-10">
<table class="min-w-full bg-gray-800 rounded-xl shadow-xl text-center">
<thead class="bg-gray-700">
<tr>
<th class="px-6 py-3 text-orange-500">Class</th>
<th class="px-6 py-3 text-orange-500">Precision</th>
<th class="px-6 py-3 text-orange-500">Recall</th>
<th class="px-6 py-3 text-orange-500">F1-Score</th>
</tr>
</thead>
<tbody>
<tr class="border-b border-gray-700">
<td class="py-4 px-6 font-semibold">Normal traffic</td>
<td class="py-4 px-6">${data.precision_normal?.toFixed(4) ?? '-'}</td>
<td class="py-4 px-6">${data.recall_normal?.toFixed(4) ?? '-'}</td>
<td class="py-4 px-6">${data.f1_normal?.toFixed(4) ?? '-'}</td>
</tr>
<tr class="border-b border-gray-700">
<td class="py-4 px-6 font-semibold">Anomaly</td>
<td class="py-4 px-6">${data.precision_attack?.toFixed(4) ?? '-'}</td>
<td class="py-4 px-6">${data.recall_attack?.toFixed(4) ?? '-'}</td>
<td class="py-4 px-6">${data.f1_attack?.toFixed(4) ?? '-'}</td>
</tr>
<tr class="bg-gray-700 text-white font-bold">
<td class="py-4 px-6">Overall accuracy</td>
<td class="py-4 px-6">-</td>
<td class="py-4 px-6">-</td>
<td class="py-4 px-6">${data.accuracy?.toFixed(4) ?? '-'}</td>
</tr>
</tbody>
</table>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 mb-10">
<div class="flex flex-col items-center">
<h3 class="text-2xl text-orange-5oo font-semibold mb-4">Class distribution</h3>
<canvas id="pieChart" class="w-80 h-80"></canvas>
</div>
<div class="bg-gray-800/60 rounded-xl p-8 border border-gray-700">
<h3 class="text-2xl text-orange-500 font-semibold mb-6">Performance</h3>
<div class="space-y-4 text-lg">
<div class="flex justify-between"><span class="text-gray-400">Normal traffic:</span> <span class="font-bold text-white">${Math.round(data.normal_count || 0)}</span></div>
<div class="flex justify-between"><span class="text-gray-400">Anomalies:</span> <span class="font-bold text-white">${Math.round(data.attack_count || 0)}</span></div>
<div class="flex justify-between"><span class="text-gray-400">Runtime:</span> <span class="font-bold text-white">${runtime} s</span></div>
<div class="flex justify-between"><span class="text-gray-400">RAM before:</span> <span class="font-bold text-white">${data.ram_before || 0} MB</span></div>
<div class="flex justify-between"><span class="text-gray-400">RAM peak:</span> <span class="font-bold text-white">${data.ram_peak || 0} MB</span></div>
<div class="flex justify-between"><span class="text-gray-400">RAM after:</span> <span class="font-bold text-white">${data.ram_after || 0} MB</span></div>
<div class="flex justify-between"><span class="text-red-400 font-semibold">RAM increase:</span> <span class="font-bold text-red-300">+${data.ram_increase || 0} MB</span></div>
</div>
</div>
</div>
${topAnomaliesHTML}
<div class="w-full flex justify-center gap-10 mt-12">
<button id="downloadPDF"
class="px-10 py-5 bg-teal-500 hover:bg-teal-400 text-white text-xl font-bold rounded-xl transition shadow-xl cursor-pointer transform hover:scale-105">
Download PDF
</button>
<button id="downloadJSON"
class="px-10 py-5 bg-yellow-500 hover:bg-yellow-400 text-white text-xl font-bold rounded-xl transition shadow-xl cursor-pointer transform hover:scale-105">
Download JSON
</button>
</div>
`;
loadPieChart(data);
document.getElementById("downloadPDF").addEventListener("click", () => {
downloadPDF(data, runtime);
});
document.getElementById("downloadJSON").addEventListener("click", () => {
downloadJSON(data, runtime);
});
}
function downloadPDF(results, runtime) {
fetch("/download-pdf", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
algorithm: chosenAlgorithmName,
results: results,
runtime: runtime
})
})
.then(response => response.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "report.pdf";
link.click();
});
}
function downloadJSON(results, runtime) {
fetch("/download-json", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
algorithm: chosenAlgorithmName,
results: results,
runtime: runtime
})
})
.then(res => res.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "report.json";
link.click();
});
}
function loadPieChart(data) {
const ctx = document.getElementById("pieChart");
new Chart(ctx, {
type: "pie",
data: {
labels: ["Normal Traffic", "Attacks"],
datasets: [{
data: [data.normal_count || 0, data.attack_count || 0],
backgroundColor: ["#14b8a6", "#dc2626"],
}]
},
options: {
plugins: {
legend: {
labels: {
color: "#ffffff"
}
}
}
}
});
}

51
Frontend/img/FEI_logo.svg Normal file
View File

@ -0,0 +1,51 @@
<svg width="367" height="96" viewBox="0 0 367 96" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_82_716)">
<mask id="mask0_82_716" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="367" height="96">
<path d="M367 0H0V96H367V0Z" fill="white"/>
</mask>
<g mask="url(#mask0_82_716)">
<path d="M0 96H95.8924V0H0V96ZM79.3642 57.9239C79.3642 69.7785 69.7211 79.4325 57.8797 79.4325H38.0335C26.1713 79.4325 16.5489 69.7785 16.5489 57.9239V16.5467H79.3642V57.9239Z" fill="black"/>
<path d="M39.6718 56.2838H56.2207V36.4152H76.0462V19.8685H19.8463V36.4152H39.6718V56.2838Z" fill="black"/>
<path d="M38.0335 76.1315H57.8797C67.8962 76.1315 76.0669 67.9724 76.0669 57.9239V39.7163H59.5388V59.5848H36.3952V39.7163H19.867V57.9239C19.867 67.9724 28.017 76.1315 38.0542 76.1315" fill="black"/>
<path d="M115.428 36.083V43.0381H112.69V26.4706H123.64V28.8789H115.428V33.7163H123.121V36.083H115.428Z" fill="black"/>
<path d="M133.283 41.6678C132.682 42.6644 131.458 43.308 129.384 43.308C126.253 43.308 125.154 41.6471 125.154 39.5294C125.154 36.9966 126.688 35.7301 129.695 35.7301H133.117V34.7751C133.117 33.3841 132.516 32.263 130.566 32.263C128.824 32.263 127.974 33.0104 127.974 34.0069V34.1938H125.527V34.0069C125.527 31.9723 127.062 30.2076 130.649 30.2076C134.507 30.2076 135.73 32.2422 135.73 34.8582V43.0381H133.407L133.283 41.6678ZM133.117 38.5329V37.6194H130.089C128.451 37.6194 127.787 38.2422 127.787 39.4256C127.787 40.4637 128.306 41.2734 130.089 41.2734C132.246 41.2734 133.117 40.1108 133.117 38.5537" fill="black"/>
<path d="M141.495 43.038H138.903V25.7646H141.495V36.0622L146.535 30.4567H149.625L144.855 35.7508L149.936 43.038H146.866L143.134 37.6401L141.495 39.4463V43.038Z" fill="black"/>
<path d="M159.807 41.3772C159.102 42.5606 157.733 43.2872 155.95 43.2872C153.171 43.2872 151.532 41.4187 151.532 38.7821V30.436H154.125V38.18C154.125 39.654 154.643 40.9827 156.738 40.9827C158.957 40.9827 159.62 39.4464 159.62 37.6402V30.4568H162.233V43.0381H159.911L159.765 41.3772H159.807Z" fill="black"/>
<path d="M168.206 25.7646H165.593V43.038H168.206V25.7646Z" fill="black"/>
<path d="M178.347 43.0381H176.708C172.955 43.0381 172.146 41.2734 172.146 38.9896V32.5121H170.425V30.4983H172.146V27.2803H174.759V30.4983H177.994V32.5121H174.759V38.8235C174.759 40.173 175.277 40.8581 177.102 40.8581H178.326V43.0381H178.347Z" fill="black"/>
<path d="M188.011 41.6678C187.409 42.6644 186.186 43.308 184.112 43.308C180.98 43.308 179.881 41.6471 179.881 39.5294C179.881 36.9966 181.416 35.7301 184.423 35.7301H187.845V34.7751C187.845 33.3841 187.243 32.263 185.294 32.263C183.552 32.263 182.702 33.0104 182.702 34.0069V34.1938H180.255V34.0069C180.255 31.9723 181.789 30.2076 185.377 30.2076C189.234 30.2076 190.458 32.2422 190.458 34.8582V43.0381H188.135L188.011 41.6678ZM187.845 38.5329V37.6194H184.817C183.179 37.6194 182.515 38.2422 182.515 39.4256C182.515 40.4637 183.033 41.2734 184.817 41.2734C186.974 41.2734 187.845 40.1108 187.845 38.5537" fill="black"/>
<path d="M203.771 43.2872C199.479 43.2872 198.338 40.7128 198.338 38.3253V35.2319C198.338 32.8443 199.624 30.1869 203.751 30.1869C207.877 30.1869 209.039 32.8443 209.039 35.2319V37.4741H200.91V38.5744C200.91 39.9447 201.594 41.2734 203.771 41.2734C205.949 41.2734 206.509 40.2353 206.509 39.3011V39.1142H208.935V39.3634C208.935 41.045 207.981 43.2665 203.771 43.2665M200.91 35.5641H206.53V34.8582C206.53 33.571 205.845 32.18 203.709 32.18C201.573 32.18 200.91 33.5917 200.91 34.8582V35.5641Z" fill="black"/>
<path d="M214.472 25.7646H211.859V43.038H214.472V25.7646Z" fill="black"/>
<path d="M222.809 43.2872C218.516 43.2872 217.375 40.7128 217.375 38.3253V35.2319C217.375 32.8443 218.661 30.1869 222.788 30.1869C226.915 30.1869 228.076 32.8443 228.076 35.2319V37.4741H219.947V38.5744C219.947 39.9447 220.631 41.2734 222.809 41.2734C224.986 41.2734 225.546 40.2353 225.546 39.3011V39.1142H227.973V39.3634C227.973 41.045 227.019 43.2665 222.809 43.2665M219.947 35.5641H225.567V34.8582C225.567 33.571 224.883 32.18 222.747 32.18C220.611 32.18 219.947 33.5917 219.947 34.8582V35.5641Z" fill="black"/>
<path d="M233.468 43.038H230.855V25.7646H233.468V36.0622L238.487 30.4567H241.597L236.828 35.7508L241.888 43.038H238.819L235.086 37.6401L233.468 39.4463V43.038Z" fill="black"/>
<path d="M250.432 43.0381H248.794C245.04 43.0381 244.231 41.2734 244.231 38.9896V32.5121H242.51V30.4983H244.231V27.2803H246.844V30.4983H250.079V32.5121H246.844V38.8235C246.844 40.173 247.363 40.8581 249.188 40.8581H250.411V43.0381H250.432Z" fill="black"/>
<path d="M252.651 43.0381V30.4568H254.973L255.139 32.4914C255.844 31.1419 257.317 30.2491 259.577 30.2491V32.7613H259.08C256.321 32.7613 255.243 34.2976 255.243 36.4775V43.0588H252.651V43.0381Z" fill="black"/>
<path d="M260.987 35.2319C260.987 32.8443 262.273 30.1869 266.462 30.1869C270.651 30.1869 271.958 32.8443 271.958 35.2319V38.263C271.958 40.6713 270.714 43.308 266.462 43.308C262.211 43.308 260.987 40.6921 260.987 38.263V35.2319ZM269.366 35.2111C269.366 33.8409 268.681 32.3461 266.462 32.3461C264.243 32.3461 263.58 33.8409 263.58 35.2111V38.2838C263.58 39.654 264.264 41.1696 266.462 41.1696C268.661 41.1696 269.366 39.654 269.366 38.2838V35.2111Z" fill="black"/>
<path d="M281.539 43.0381H279.9C276.147 43.0381 275.338 41.2734 275.338 38.9896V32.5121H273.617V30.4983H275.338V27.2803H277.951V30.4983H281.186V32.5121H277.951V38.8235C277.951 40.173 278.47 40.8581 280.295 40.8581H281.518V43.0381H281.539Z" fill="black"/>
<path d="M288.673 43.2872C284.38 43.2872 283.239 40.7128 283.239 38.3253V35.2319C283.239 32.8443 284.525 30.1869 288.652 30.1869C292.779 30.1869 293.94 32.8443 293.94 35.2319V37.4741H285.811V38.5744C285.811 39.9447 286.495 41.2734 288.673 41.2734C290.85 41.2734 291.41 40.2353 291.41 39.3011V39.1142H293.836V39.3634C293.836 41.045 292.882 43.2665 288.673 43.2665M285.811 35.5641H291.431V34.8582C291.431 33.571 290.746 32.18 288.61 32.18C286.474 32.18 285.811 33.5917 285.811 34.8582V35.5641Z" fill="black"/>
<path d="M306.984 38.4914V38.7198C306.984 40.8997 305.595 43.2872 301.758 43.2872C297.569 43.2872 296.304 40.6713 296.304 38.2423V35.2111C296.304 32.8236 297.59 30.1661 301.758 30.1661C305.699 30.1661 306.964 32.5744 306.964 34.7336V34.962H304.413V34.7128C304.413 33.571 303.749 32.3253 301.779 32.3253C299.602 32.3253 298.917 33.8201 298.917 35.1904V38.2423C298.917 39.6125 299.602 41.1073 301.779 41.1073C303.77 41.1073 304.413 39.8616 304.413 38.7198V38.4914H306.984Z" fill="black"/>
<path d="M309.618 43.038V25.7646H312.21V31.91C312.936 30.8512 314.263 30.1868 315.985 30.1868C318.888 30.1868 320.505 32.1799 320.505 34.9826V43.0172H317.892V35.5847C317.892 33.9446 317.353 32.4913 315.197 32.4913C312.936 32.4913 312.231 34.0484 312.231 35.8339V43.0172H309.639L309.618 43.038Z" fill="black"/>
<path d="M323.699 43.0381V30.4568H326.022L326.167 32.1592C326.872 30.9343 328.261 30.2076 330.086 30.2076C332.99 30.2076 334.607 32.2007 334.607 35.0035V43.0381H331.994V35.6056C331.994 33.9654 331.455 32.5121 329.298 32.5121C327.038 32.5121 326.333 34.0692 326.333 35.8547V43.0381H323.741H323.699Z" fill="black"/>
<path d="M337.78 25.7646H340.455V28.2975H337.78V25.7646ZM337.801 30.4567H340.414V43.038H337.801V30.4567Z" fill="black"/>
<path d="M346.345 43.038H343.753V25.7646H346.345V36.0622L351.384 30.4567H354.474L349.705 35.7508L354.785 43.038H351.716L347.983 37.6401L346.345 39.4463V43.038Z" fill="black"/>
<path d="M358.269 47.2942L359.866 43.0173L355.325 30.4568H358.083L361.131 39.5295L364.387 30.4568H367L360.758 47.2942H358.269Z" fill="black"/>
<path d="M119.865 68.1592C119.264 69.1557 118.041 69.7993 115.967 69.7993C112.835 69.7993 111.736 68.1384 111.736 66.0208C111.736 63.4879 113.271 62.2215 116.278 62.2215H119.7V61.2664C119.7 59.8754 119.098 58.7543 117.149 58.7543C115.407 58.7543 114.557 59.5017 114.557 60.4983V60.6851H112.109V60.4983C112.109 58.4637 113.644 56.699 117.232 56.699C121.089 56.699 122.313 58.7336 122.313 61.3495V69.5294H119.99L119.865 68.1592ZM119.7 65.0242V64.1107H116.672C115.034 64.1107 114.37 64.7336 114.37 65.917C114.37 66.955 114.888 67.7647 116.672 67.7647C118.829 67.7647 119.7 66.6021 119.7 65.045" fill="black"/>
<path d="M130.608 52.2561H133.283V54.789H130.608V52.2561ZM130.649 56.9481H133.262V69.5295H130.649V56.9481Z" fill="black"/>
<path d="M136.601 69.5294V56.9481H138.924L139.069 58.6505C139.774 57.4256 141.163 56.699 142.988 56.699C145.892 56.699 147.509 58.6921 147.509 61.4948V69.5294H144.896V62.0969C144.896 60.4568 144.357 59.0035 142.2 59.0035C139.94 59.0035 139.214 60.5606 139.214 62.346V69.5294H136.622H136.601Z" fill="black"/>
<path d="M153.855 59.0035V69.5295H151.242V59.0035H149.562V56.9897H151.242V56.616C151.242 54.2284 152.424 52.2561 156.344 52.2561H157.36V54.3115H156.219C154.498 54.3115 153.855 55.1627 153.855 56.4914V56.9897H157.049V59.0035H153.855Z" fill="black"/>
<path d="M158.438 61.7232C158.438 59.3357 159.724 56.6782 163.913 56.6782C168.102 56.6782 169.409 59.3357 169.409 61.7232V64.7543C169.409 67.1627 168.164 69.7993 163.913 69.7993C159.662 69.7993 158.438 67.1834 158.438 64.7543V61.7232ZM166.837 61.7024C166.837 60.3322 166.153 58.8374 163.934 58.8374C161.715 58.8374 161.051 60.3322 161.051 61.7024V64.7751C161.051 66.1454 161.736 67.6609 163.934 67.6609C166.132 67.6609 166.837 66.1454 166.837 64.7751V61.7024Z" fill="black"/>
<path d="M172.312 69.5294V56.9481H174.634L174.8 58.9827C175.505 57.6332 176.978 56.7405 179.238 56.7405V59.2526H178.741C175.982 59.2526 174.904 60.7889 174.904 62.9689V69.5502H172.312V69.5294Z" fill="black"/>
<path d="M181.333 69.5294V56.9481H183.656L183.801 58.6298C184.485 57.4256 185.854 56.699 187.637 56.699C189.566 56.699 190.914 57.5917 191.577 59.0658C192.22 57.5917 193.734 56.699 195.787 56.699C198.649 56.699 200.204 58.6921 200.204 61.4948V69.5294H197.612V62.0969C197.612 60.4568 197.073 59.0035 194.999 59.0035C192.78 59.0035 192.096 60.5606 192.096 62.346V69.5294H189.504V62.0969C189.504 60.4568 188.964 59.0035 186.891 59.0035C184.672 59.0035 183.987 60.5606 183.987 62.346V69.5294H181.395H181.333Z" fill="black"/>
<path d="M210.802 68.1592C210.2 69.1557 208.977 69.7993 206.903 69.7993C203.771 69.7993 202.672 68.1384 202.672 66.0208C202.672 63.4879 204.207 62.2215 207.214 62.2215H210.636V61.2664C210.636 59.8754 210.034 58.7543 208.085 58.7543C206.343 58.7543 205.493 59.5017 205.493 60.4983V60.6851H203.046V60.4983C203.046 58.4637 204.58 56.699 208.168 56.699C212.025 56.699 213.249 58.7336 213.249 61.3495V69.5294H210.926L210.802 68.1592ZM210.636 65.0242V64.1107H207.608C205.97 64.1107 205.306 64.7336 205.306 65.917C205.306 66.955 205.824 67.7647 207.608 67.7647C209.765 67.7647 210.636 66.6021 210.636 65.045" fill="black"/>
<path d="M223.161 69.5294H221.523C217.77 69.5294 216.961 67.7647 216.961 65.481V59.0034H215.24V56.9896H216.961V53.7716H219.574V56.9896H222.809V59.0034H219.574V65.3149C219.574 66.6643 220.092 67.3495 221.917 67.3495H223.141V69.5294H223.161Z" fill="black"/>
<path d="M225.38 52.2561H228.056V54.789H225.38V52.2561ZM225.401 56.9481H228.014V69.5295H225.401V56.9481Z" fill="black"/>
<path d="M233.966 69.5295H231.353V52.2561H233.966V62.5329L238.984 56.9481H242.095L237.305 62.2423L242.385 69.5295H239.316L235.583 64.1315L233.966 65.917V69.5295Z" fill="black"/>
<path d="M245.89 73.7855L247.466 69.4879L242.945 56.9481H245.704L248.752 66.0208L252.008 56.9481H254.6L248.379 73.7855H245.89Z" fill="black"/>
</g>
</g>
<defs>
<clipPath id="clip0_82_716">
<rect width="367" height="96" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

BIN
Frontend/img/kemt_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

9
docker.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/bash
echo "Staviam kontajner"
docker build -t aplikacia:dev .
echo "Spustam kontajner"
docker run --name utlak-aplikacia -p 5000:5000 aplikacia:dev

1
progress.json Normal file
View File

@ -0,0 +1 @@
{"progress": 100}

11
requirements.txt Normal file
View File

@ -0,0 +1,11 @@
flask
flask-cors
tensorflow
pandas
numpy
scikit-learn
seaborn
matplotlib
psutil
tabulate
reportlab