Changes between Version 1 and Version 2 of AdvancedApplicationDevelopment


Ignore:
Timestamp:
08/23/26 20:42:56 (4 days ago)
Author:
231118
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AdvancedApplicationDevelopment

    v1 v2  
    99=== Прием на податоци од агент (`/receive`) ===
    1010
    11 Најкритичната трансакција во системот се извршува при прием на податоци од агентите преку POST `/receive`. Во рамките на една конекција се извршуваат следните операции:
     11Најкритичната трансакција во системот се извршува при прием на податоци од агентите преку POST `/receive`. Во рамките на една конекција (v04) се извршуваат следните операции:
    1212
    1313 * Пронаоѓање или креирање на компјутер во `computers`
    1414 * Ажурирање на `last_seen`, `ip`, `os`, `user`, `sysmon_available`
    15  * Внесување на системски метрики во `computer_history`
     15 * Внесување на системски метрики во `computer_history` (историја) и освежување на тековниот snapshot во `computer_history_current`
    1616 * Бришење и повторно внесување на тековните процеси во `computer_processes_current`
    17  * Условно внесување на историски процеси во `computer_processes_history` (доколку е овозможено во `env_settings`)
     17 * Условно внесување на историски процеси во `computer_processes_history` (ако `save_process_history` е овозможено во `env_settings`)
    1818 * Внесување на Sysmon настани во `sysmon_events`
    19  * Внесување на мрежни конекции во `network_connections`
     19 * Освежување на тековните мрежни конекции во `network_connections_current` и условно во `network_connections_history` (ако `save_network_history` е овозможено)
    2020
    2121Сите операции се завршуваат со `conn.commit()`. Доколку се случи грешка во кој било чекор, се извршува `conn.rollback()` и ниедна промена не е трајно зачувана:
     
    5858            computer_id = c.lastrowid
    5959
    60         # Чекор 2: Системски метрики
     60        # Чекор 2: Системски метрики — историја + тековен snapshot [v04]
    6161        c.execute("""
    6262            INSERT INTO computer_history(
     63                computer_id, cpu_usage, ram_usage, disk_usage,
     64                network_sent_mb, network_recv_mb, timestamp)
     65            VALUES(?, ?, ?, ?, ?, ?, ?)
     66        """, (computer_id, float(info.get("cpu_usage") or 0),
     67              float(info.get("ram_usage") or 0), float(info.get("disk_usage") or 0),
     68              float(info.get("network_sent_mb") or 0),
     69              float(info.get("network_recv_mb") or 0),
     70              info.get("timestamp") or now_iso))
     71
     72        c.execute("DELETE FROM computer_history_current WHERE computer_id=?", (computer_id,))
     73        c.execute("""
     74            INSERT INTO computer_history_current(
    6375                computer_id, cpu_usage, ram_usage, disk_usage,
    6476                network_sent_mb, network_recv_mb, timestamp)
     
    109121                  json.dumps(ev, ensure_ascii=False)))
    110122
    111         # Чекор 6: Мрежни конекции
     123        # Чекор 6: Мрежни конекции — тековен snapshot + условна историја [v04]
     124        c.execute("DELETE FROM network_connections_current WHERE computer_id=?", (computer_id,))
     125        net_hist = is_network_history_enabled(tenant_id, env_name)
    112126        for nc in (security_data.get("network_connections") or []):
     127            vals = (computer_id, nc.get("pid"), nc.get("local_address"),
     128                    nc.get("remote_address"), nc.get("status"),
     129                    nc.get("process_name"), info.get("timestamp") or now_iso)
    113130            c.execute("""
    114                 INSERT INTO network_connections(
    115                     computer_id, pid, local_address,
    116                     remote_address, status, process_name, timestamp)
     131                INSERT INTO network_connections_current(
     132                    computer_id, pid, local_address, remote_address,
     133                    status, process_name, timestamp)
    117134                VALUES(?, ?, ?, ?, ?, ?, ?)
    118             """, (computer_id, nc.get("pid"), nc.get("local_address"),
    119                   nc.get("remote_address"), nc.get("status"),
    120                   nc.get("process_name"), info.get("timestamp") or now_iso))
     135            """, vals)
     136            if net_hist:
     137                c.execute("""
     138                    INSERT INTO network_connections_history(
     139                        computer_id, pid, local_address, remote_address,
     140                        status, process_name, timestamp)
     141                    VALUES(?, ?, ?, ?, ?, ?, ?)
     142                """, vals)
    121143
    122144        # Сите операции успешни — зачувај
    123145        conn.commit()
    124         return jsonify({"ok": True, "computer_id": computer_id, ...})
     146        return jsonify({"ok": True, "computer_id": computer_id})
    125147
    126148    except Exception as e:
     
    137159 * `conn.rollback()` се повикува во `except` блокот — ниедна табела нема делумни податоци
    138160 * `conn.close()` е во `finally` блокот — конекцијата секогаш се затвора
    139  * Условното снимање на процесна историја (`is_process_history_enabled`) е дел од истата трансакција
     161 * Условното снимање историја (процеси/мрежа) е дел од истата трансакција и се контролира по околина преку знамињата во `env_settings`
    140162
    141163----
     
    268290=== Ажурирање на поставки за околина (`/api/admin/env-settings/<env_name>`) ===
    269291
    270 Поставките за околина се запишуваат атомарно преку `INSERT OR ... ON CONFLICT DO UPDATE` (upsert), со цел да се избегне race condition при истовремено читање и пишување:
     292Поставките за околина се запишуваат атомарно преку upsert (`ON CONFLICT DO UPDATE`), со цел да се избегне race condition. Во v04 се поддржани трите знамиња (`save_process_history`, `save_metrics_history`, `save_network_history`); незададените во барањето ги задржуваат тековните вредности:
    271293
    272294{{{
     
    275297def admin_set_env_settings(env_name):
    276298    tenant_id = request.user["tenant_id"]
    277     enabled = 1 if bool(data.get("save_process_history")) else 0
     299    data = request.get_json(force=True, silent=True) or {}
    278300
    279301    conn = db()
    280302    c = conn.cursor()
     303
     304    # прочитај ги тековните вредности за да ги задржиш незададените знамиња
    281305    c.execute("""
    282         INSERT INTO env_settings(tenant_id, env_name, save_process_history, created_at, updated_at)
    283         VALUES(?, ?, ?, datetime('now'), datetime('now'))
     306        SELECT save_process_history, save_metrics_history, save_network_history
     307        FROM env_settings WHERE tenant_id=? AND env_name=? LIMIT 1
     308    """, (tenant_id, env_name))
     309    cur = c.fetchone()
     310
     311    def pick(key):
     312        if data.get(key) is not None:
     313            return 1 if bool(data.get(key)) else 0
     314        return int(cur[key]) if cur else 0
     315
     316    p = pick("save_process_history")
     317    m = pick("save_metrics_history")
     318    n = pick("save_network_history")
     319
     320    c.execute("""
     321        INSERT INTO env_settings(tenant_id, env_name, save_process_history,
     322                                 save_metrics_history, save_network_history,
     323                                 created_at, updated_at)
     324        VALUES(?, ?, ?, ?, ?, datetime('now'), datetime('now'))
    284325        ON CONFLICT(tenant_id, env_name) DO UPDATE SET
    285326            save_process_history=excluded.save_process_history,
     327            save_metrics_history=excluded.save_metrics_history,
     328            save_network_history=excluded.save_network_history,
    286329            updated_at=datetime('now')
    287     """, (tenant_id, env_name, enabled))
     330    """, (tenant_id, env_name, p, m, n))
    288331    conn.commit()
    289332    conn.close()
    290333
    291     return jsonify({"ok": True, "env": env_name, "save_process_history": bool(enabled)})
     334    return jsonify({"ok": True, "env": env_name,
     335                    "save_process_history": bool(p),
     336                    "save_metrics_history": bool(m),
     337                    "save_network_history": bool(n)})
    292338}}}
    293339
     
    296342== Database Connection Pooling ==
    297343
    298 Во моменталната имплементација системот користи SQLite база на податоци преку `sqlite3` библиотеката. Конекцијата кон базата се воспоставува преку помошната функција `db()`:
     344Во моменталната имплементација (прототип) системот користи SQLite база преку `sqlite3` библиотеката. Конекцијата се воспоставува преку помошната функција `db()`:
    299345
    300346{{{
     
    307353}}}
    308354
    309 `conn.row_factory = sqlite3.Row` овозможува пристап до колоните по име (на пр. `row["email"]`) наместо по индекс. При секое барање се отвора нова конекција која се затвора по завршување на операцијата — секогаш во `finally` блокот:
     355`conn.row_factory = sqlite3.Row` овозможува пристап до колоните по име (на пр. `row["email"]`). При секое барање се отвора нова конекција која се затвора по завршување — секогаш во `finally` блокот:
    310356
    311357{{{
     
    320366}}}
    321367
    322 Поради природата на SQLite (file-based база со writer lock), Connection Pooling не е применливо во тековната верзија.
     368Поради природата на SQLite (file-based база со writer lock), Connection Pooling не е применливо во тековната прототип верзија.
     369
     370Забелешка: официјалната проектна база (од претходната фаза) е PostgreSQL. Прототипот користи SQLite со истата структура, а миграцијата кон PostgreSQL со pool е опишана подолу.
    323371
    324372----
     
    326374=== Позадинска задача за автоматско чистење (`cleanup_old_data`) ===
    327375
    328 Системот стартува посебна daemon нишка која на секои 3600 секунди (1 час) автоматски ги брише записите постари од 30 дена. Нишката ја користи истата `db()` функција и работи со своја независна конекција:
     376Системот стартува посебна daemon нишка која на секои 3600 секунди (1 час) автоматски ги брише записите постари од 30 дена од историските табели. Нишката работи со своја независна конекција:
    329377
    330378{{{
     
    342390            c.execute("DELETE FROM network_connections WHERE timestamp < ?", (cutoff,))
    343391            c.execute("DELETE FROM computer_processes_history WHERE timestamp < ?", (cutoff,))
     392            # [v04] нова history табела
     393            c.execute("DELETE FROM network_connections_history WHERE timestamp < ?", (cutoff,))
    344394
    345395            conn.commit()
     
    355405
    356406Нишката е `daemon=True` — автоматски се гасне кога се гасне главниот Flask процес.
    357 
    358 ----
    359 
    360 === Планирана миграција кон PostgreSQL со Connection Pooling ===
    361 
    362 По миграција кон PostgreSQL, `db()` функцијата би се заменила со SQLAlchemy engine со connection pool:
     407(Тековните snapshot табели `*_current` не се чистат бидејќи се препишуваат при секое известување.)
     408
     409----
     410
     411=== Миграција кон PostgreSQL со Connection Pooling ===
     412
     413Официјалната проектна база е веќе PostgreSQL. При поврзување на апликацискиот слој кон неа, `db()` функцијата би се заменила со SQLAlchemy engine со connection pool:
    363414
    364415{{{
     
    408459Предности на оваа миграција:
    409460 * Повторно користење на активни конекции наместо отворање нова при секое барање
    410  * Намалување на латенцијата — воспоставувањето на нова PostgreSQL конекција трае ~50-100ms
     461 * Намалување на латенцијата — воспоставувањето нова PostgreSQL конекција трае ~50-100ms
    411462 * `pool_pre_ping=True` автоматски ги отстранува прекинатите конекции од pool-от
    412463 * Подобри перформанси при голем број истовремени агенти кои праќаат податоци паралелно