-
- -
- - -
-
-
Stamdata
-
-
-
- Type - {{ hardware.asset_type|title }} -
- {% if hardware.internal_asset_id %} -
- Intern ID - {{ hardware.internal_asset_id }} -
- {% endif %} - {% if hardware.customer_asset_id %} -
- Kunde ID - {{ hardware.customer_asset_id }} -
- {% endif %} - {% if hardware.warranty_until %} -
- Garanti Udløb - {{ hardware.warranty_until }} -
- {% endif %} - {% if hardware.end_of_life %} -
- End of Life - {{ hardware.end_of_life }} -
- {% endif %} -
-
+ + - -
-
-
AnyDesk
-
-
-
- AnyDesk ID - {{ hardware.anydesk_id or '-' }} -
-
- AnyDesk Link - - {% if hardware.anydesk_link %} - Connect - {% else %} - - - {% endif %} - -
-
-
- - -
-
-
Tags
- -
-
-
- -
- +
+ + +
+
+ +
+ +
+
+
Stamdata
-
-
-
- - -
-
-
Nuværende Lokation
- -
-
- {% if locations and locations|length > 0 %} - {% set current_loc = locations[0] %} - {% if not current_loc.end_date %} -
-
- -
-
-
{{ current_loc.location_name or 'Ukendt' }}
- Siden {{ current_loc.start_date }} -
-
- {% if current_loc.notes %} -
- {{ current_loc.notes }} -
- {% endif %} - {% else %} -
- -

Ingen aktiv lokation

-
- {% endif %} - {% else %} -
-

Hardwaret er ikke tildelt en lokation

- -
- {% endif %} -
-
- - -
-
-
Nuværende Ejer
-
-
- {% if ownership and ownership|length > 0 %} - {% set current_own = ownership[0] %} - {% if not current_own.end_date %} -
-
- -
-
-
- {{ current_own.customer_name or current_own.owner_type|title }} -
- Siden {{ current_own.start_date }} -
-
- {% else %} -

Ingen aktiv ejer registreret

- {% endif %} - {% else %} -

Ingen ejerhistorik

- {% endif %} -
-
- -
- - -
- - -
-
-
- -
Opret Sag
-
-
-
-
- -
Skift Lokation
-
-
- -
-
- -
Tilføj Bilag
-
-
-
- - -
-
- -
-
-
- - -
-
Kombineret Historik
- -
- - -
- Placeringer - {% if locations %} - {% for loc in locations %} -
-
-
-
{{ loc.location_name or 'Ukendt' }} ({{ loc.start_date }} {% if loc.end_date %} - {{ loc.end_date }}{% else %}- nu{% endif %})
- {% if loc.notes %}
{{ loc.notes }}
{% endif %} -
-
- {% endfor %} - {% else %} -

Ingen lokations historik

+
+
+ Type + + {% if hardware.asset_type == 'pc' %}🖥️ PC + {% elif hardware.asset_type == 'laptop' %}💻 Laptop + {% elif hardware.asset_type == 'printer' %}🖨️ Printer + {% elif hardware.asset_type == 'skærm' %}🖥️ Skærm + {% elif hardware.asset_type == 'telefon' %}📱 Telefon + {% elif hardware.asset_type == 'server' %}🗄️ Server + {% elif hardware.asset_type == 'netværk' %}🌐 Netværk + {% else %}📦 {{ hardware.asset_type|title }} {% endif %} -
- -
- Ejerskab - {% if ownership %} - {% for own in ownership %} -
-
-
-
{{ own.customer_name or own.owner_type }} ({{ own.start_date }} {% if own.end_date %} - {{ own.end_date }}{% else %}- nu{% endif %})
- {% if own.notes %}
{{ own.notes }}
{% endif %} -
-
- {% endfor %} + +
+
+ Mærke/Model + {{ hardware.brand or '-' }} / {{ hardware.model or '-' }} +
+ {% if hardware.internal_asset_id %} +
+ Internt Asset ID + {{ hardware.internal_asset_id }} +
+ {% endif %} + {% if hardware.customer_asset_id %} +
+ Kunde Asset ID + {{ hardware.customer_asset_id }} +
+ {% endif %} + {% if hardware.warranty_until %} +
+ Garanti til + {{ hardware.warranty_until }} +
+ {% endif %} + {% if hardware.end_of_life %} +
+ End of Life + {{ hardware.end_of_life }} +
+ {% endif %} +
+
+ + + {% set anydesk_url = hardware.anydesk_id and ('anydesk://' ~ hardware.anydesk_id) %} +
+
+
Remote Access
+ + Ændre + +
+
+
+ AnyDesk ID + + {% if anydesk_url %} + {{ hardware.anydesk_id }} {% else %} -

Ingen ejerskabs historik

+ - + {% endif %} +
+
+
+ Handling + + {% if anydesk_url %} + + Connect AnyDesk + + {% else %} + Ingen link + {% endif %} + +
+
+
+ + +
+
+
+
+
Lokation
+ +
+
+ {% if current_loc and not current_loc.end_date %} +
+
+
{{ current_loc.location_name }}
+

Siden: {{ current_loc.start_date }}

+ {% if current_loc.notes %} +
"{{ current_loc.notes }}"
+ {% endif %} +
+ {% else %} +
+

Ingen aktiv lokation

+ +
{% endif %}
+
+
+
+
Ejer
+
+
+ {% if current_owner and not current_owner.end_date %} +
+
+
{{ current_owner.customer_name or current_owner.owner_type|title }}
+

Siden: {{ current_owner.start_date }}

+
+ {% else %} +
+

Ingen aktiv ejer

+
+ {% endif %} +
+
+
+
+
- -
+ +
+ +
+ +
+
+ + Skift Lokation +
+
+ +
+
+ + Tilføj Bilag +
+
+
+ + +
+
+
Seneste Sager
+
+
{% if cases and cases|length > 0 %} - +
+
+
- -
-
- {% if attachments %} - {% for att in attachments %} -
-
-
📎
-
{{ att.file_name }}
-
{{ att.uploaded_at }}
+ +
+
+
+
+
+
Lokations Historik
+
+ {% if locations %} + {% for loc in locations %} +
+
+
+
{{ loc.location_name or 'Ukendt' }}
+
+ {{ loc.start_date }} + {% if loc.end_date %} - {{ loc.end_date }}{% else %} Nuværende{% endif %} +
+ {% if loc.notes %}
"{{ loc.notes }}"
{% endif %} +
-
- {% endfor %} - {% else %} -
- Ingen filer vedhæftet -
- {% endif %} + {% endfor %} + {% else %} +

Ingen historik.

+ {% endif %}
- - -
-
- {% if hardware.notes %} - {{ hardware.notes }} +
+
Ejerskabs Historik
+
+ {% if ownership %} + {% for own in ownership %} +
+
+
+
{{ own.customer_name or own.owner_type }}
+
+ {{ own.start_date }} + {% if own.end_date %} - {{ own.end_date }}{% else %} Nuværende{% endif %} +
+ {% if own.notes %}
"{{ own.notes }}"
{% endif %} +
+
+ {% endfor %} {% else %} - Ingen noter... +

Ingen historik.

{% endif %}
@@ -540,6 +479,54 @@
+ + +
+
+
+
+ {% if attachments %} + {% for att in attachments %} +
+
+
📎
+
{{ att.file_name }}
+
{{ att.uploaded_at }}
+ +
+
+ {% endfor %} + {% else %} +
+ +

Ingen filer vedhæftet

+ +
+ {% endif %} +
+
+
+
+ + +
+
+
+
Noter
+
+ {% if hardware.notes %} +
{{ hardware.notes }}
+ {% else %} + Ingen noter tilføjet... + {% endif %} +
+ +
+
+
+
@@ -693,19 +680,6 @@ } } } else { - // Hide if not a match and doesn't contain matches - // BUT be careful not to hide if a parent matched? - // Actually, search usually filters down. If parent matches, should we show all children? - // Let's stick to showing matches and path to matches. - - // Important: logic is tricky with flat recursion vs nested DOM - // My macro structure is nested: .location-item-container contains children-container which contains .location-item-container - // So `container.style.display = 'block'` on a parent effectively shows the wrapper. - - // If I am not a match, and I have no children that are matches... - // But wait, if my parent is a match, do I show up? - // Usually "Search" filters items out. - if (isMatch || hasChildMatch) { container.style.display = 'block'; } else { @@ -758,4 +732,4 @@ } }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/modules/hardware/templates/edit.html b/app/modules/hardware/templates/edit.html index 4173827..0494057 100644 --- a/app/modules/hardware/templates/edit.html +++ b/app/modules/hardware/templates/edit.html @@ -210,6 +210,15 @@
+ {% if hardware.anydesk_id %} +
+ {% if hardware.anydesk_link %} + Test forbindelse + {% else %} + Test forbindelse + {% endif %} +
+ {% endif %}
diff --git a/app/modules/hardware/templates/eset_import.html b/app/modules/hardware/templates/eset_import.html new file mode 100644 index 0000000..2533012 --- /dev/null +++ b/app/modules/hardware/templates/eset_import.html @@ -0,0 +1,293 @@ +{% extends "shared/frontend/base.html" %} + +{% block title %}ESET Import - Hardware - BMC Hub{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ + +
+
+
Ingen data indlaest
+ +
+
+ + + + + + + + + + + + + + + +
NavnSerialGruppeDevice UUIDHandling
Klik "Hent devices" for at hente ESET-listen.
+
+
+
+ + + +{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/app/modules/hardware/templates/eset_overview.html b/app/modules/hardware/templates/eset_overview.html new file mode 100644 index 0000000..0cc3ad5 --- /dev/null +++ b/app/modules/hardware/templates/eset_overview.html @@ -0,0 +1,163 @@ +{% extends "shared/frontend/base.html" %} + +{% block title %}ESET Oversigt - Hardware - BMC Hub{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ + +
+
🔗 Matchede enheder
+ {% if matches %} +
+ + + + + + + + + + + + + + + {% for row in matches %} + + + + + + + + + + + {% endfor %} + +
HardwareSerialESET UUIDESET GruppeKontaktCompanyKundeOpdateret
+ + {{ row.brand or '' }} {{ row.model or '' }} + + {{ row.serial_number or '-' }}{{ row.eset_uuid or '-' }}{{ row.eset_group or '-' }} + {% if row.contact_id %} + {{ row.first_name or '' }} {{ row.last_name or '' }} + {% else %} + - + {% endif %} + {{ row.user_company or '-' }}{{ row.customer_name or '-' }}{{ row.updated_at or '-' }}
+
+ {% else %} +
Ingen matchede enheder fundet endnu.
+ {% endif %} +
+ +
+
🚨 Kritiske incidents
+ {% if incidents %} +
+ + + + + + + + + + + + {% for inc in incidents %} + + + + + + + + {% endfor %} + +
SeverityStatusDevice UUIDDetectedLast Seen
+ {% set sev = (inc.severity or '')|lower %} + + {{ inc.severity or 'critical' }} + + {{ inc.status or '-' }}{{ inc.device_uuid or '-' }}{{ inc.detected_at or '-' }}{{ inc.last_seen or '-' }}
+
+ {% else %} +
Ingen kritiske incidents lige nu.
+ {% endif %} +
+
+{% endblock %} diff --git a/app/modules/hardware/templates/eset_test.html b/app/modules/hardware/templates/eset_test.html new file mode 100644 index 0000000..d3a4cf5 --- /dev/null +++ b/app/modules/hardware/templates/eset_test.html @@ -0,0 +1,121 @@ +{% extends "shared/frontend/base.html" %} + +{% block title %}ESET Test - Hardware - BMC Hub{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+ + +
+
Test device list (raw)
+
+ + +
+
Tryk "Hent devices" for at teste forbindelsen.
+
+ +
+
Test single device
+
+ + +
+
Indtast en UUID og klik "Hent device".
+
+
+{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/app/modules/hardware/templates/index.html b/app/modules/hardware/templates/index.html index 6626e9a..16b7fc6 100644 --- a/app/modules/hardware/templates/index.html +++ b/app/modules/hardware/templates/index.html @@ -242,10 +242,16 @@ {% block content %}
@@ -319,6 +325,18 @@ Type: {{ item.asset_type|title }}
+ {% if item.anydesk_id or item.anydesk_link %} +
+ AnyDesk: + + {% if item.anydesk_link %} + {{ item.anydesk_id or 'Åbn' }} + {% elif item.anydesk_id %} + {{ item.anydesk_id }} + {% endif %} + +
+ {% endif %} {% if item.customer_name %}
Ejer: diff --git a/app/modules/sag/templates/create.html b/app/modules/sag/templates/create.html index f8604c6..063ac53 100644 --- a/app/modules/sag/templates/create.html +++ b/app/modules/sag/templates/create.html @@ -220,10 +220,7 @@
-
-
- - +
Link genereres automatisk.
@@ -1341,6 +1342,21 @@ checkMaintenanceMode(); } }, 30000); + + // Global Logout Function + function logoutUser(event) { + if (event) event.preventDefault(); + + // Clear local storage + localStorage.removeItem('access_token'); + localStorage.removeItem('user'); + + // Clear cookies + document.cookie = "access_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + + // Redirect to login + window.location.href = '/login'; + } {% block scripts %}{% endblock %} diff --git a/app/timetracking/frontend/service_contract_wizard.html b/app/timetracking/frontend/service_contract_wizard.html index d29964a..ccd2ad5 100644 --- a/app/timetracking/frontend/service_contract_wizard.html +++ b/app/timetracking/frontend/service_contract_wizard.html @@ -848,7 +848,7 @@ } // vTiger's 'duration' field is in seconds, convert to minutes - if (fieldName === 'duration' && typeof rawValue !== 'string') { + if (fieldName === 'duration') { const seconds = typeof rawValue === 'number' ? rawValue : parseFloat(String(rawValue)); if (Number.isFinite(seconds)) { return seconds / 60; @@ -1002,6 +1002,17 @@ const row = document.createElement('tr'); const hoursData = getTimelogHours(item); const hours = normalizeTimelogHours(hoursData.value, hoursData.field); + + // DEBUG: Log første entry for at verificere parsing + if (timelogs.indexOf(item) === 0) { + console.log('🔍 TIMELOG DEBUG:', { + id: item.id, + hoursData: hoursData, + parsedHours: hours, + rawItem: item + }); + } + const relatedId = getTimelogRelatedId(item) || '-'; const relatedCase = caseMap.get(relatedId); const caseNumber = relatedCase diff --git a/main.py b/main.py index 03c0b9e..df12118 100644 --- a/main.py +++ b/main.py @@ -124,6 +124,19 @@ async def lifespan(app: FastAPI): replace_existing=True ) logger.info("✅ Reminder job scheduled (every 5 minutes)") + + if settings.ESET_ENABLED and settings.ESET_SYNC_ENABLED: + from app.jobs.eset_sync import run_eset_sync + + backup_scheduler.scheduler.add_job( + func=run_eset_sync, + trigger=IntervalTrigger(minutes=settings.ESET_SYNC_INTERVAL_MINUTES), + id='eset_sync', + name='ESET Sync', + max_instances=1, + replace_existing=True + ) + logger.info("✅ ESET sync job scheduled (every %d minutes)", settings.ESET_SYNC_INTERVAL_MINUTES) logger.info("✅ System initialized successfully") yield diff --git a/migrations/117_add_eset_fields.sql b/migrations/117_add_eset_fields.sql new file mode 100644 index 0000000..7480ed4 --- /dev/null +++ b/migrations/117_add_eset_fields.sql @@ -0,0 +1,8 @@ +-- Add ESET integration fields to hardware_assets +-- Enables storing ESET UUID and raw hardware specs + +ALTER TABLE hardware_assets +ADD COLUMN IF NOT EXISTS eset_uuid VARCHAR(100), +ADD COLUMN IF NOT EXISTS hardware_specs JSONB; + +CREATE INDEX IF NOT EXISTS idx_hardware_eset_uuid ON hardware_assets(eset_uuid); diff --git a/migrations/118_eset_sync.sql b/migrations/118_eset_sync.sql new file mode 100644 index 0000000..5fa432f --- /dev/null +++ b/migrations/118_eset_sync.sql @@ -0,0 +1,30 @@ +-- ESET sync support: hardware contacts + incidents cache + +CREATE TABLE IF NOT EXISTS hardware_contacts ( + id SERIAL PRIMARY KEY, + hardware_id INT NOT NULL REFERENCES hardware_assets(id) ON DELETE CASCADE, + contact_id INT NOT NULL REFERENCES contacts(id) ON DELETE CASCADE, + role VARCHAR(50) DEFAULT 'primary', + source VARCHAR(50) DEFAULT 'eset', + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE(hardware_id, contact_id) +); + +CREATE INDEX IF NOT EXISTS idx_hardware_contacts_hardware ON hardware_contacts(hardware_id); +CREATE INDEX IF NOT EXISTS idx_hardware_contacts_contact ON hardware_contacts(contact_id); + +CREATE TABLE IF NOT EXISTS eset_incidents ( + id SERIAL PRIMARY KEY, + incident_uuid VARCHAR(100) UNIQUE, + severity VARCHAR(50), + status VARCHAR(50), + device_uuid VARCHAR(100), + detected_at TIMESTAMP, + last_seen TIMESTAMP, + payload JSONB, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_eset_incidents_severity ON eset_incidents(severity); +CREATE INDEX IF NOT EXISTS idx_eset_incidents_device ON eset_incidents(device_uuid); diff --git a/migrations/119_add_eset_group.sql b/migrations/119_add_eset_group.sql new file mode 100644 index 0000000..af7af1f --- /dev/null +++ b/migrations/119_add_eset_group.sql @@ -0,0 +1,6 @@ +-- Add ESET group field to hardware_assets + +ALTER TABLE hardware_assets +ADD COLUMN IF NOT EXISTS eset_group TEXT; + +CREATE INDEX IF NOT EXISTS idx_hardware_eset_group ON hardware_assets(eset_group);