bmc_hub/app/modules/hardware/templates/index.html
Christian 29acdf3e01 Add tests for new SAG module endpoints and module deactivation
- Implement test script for new SAG module endpoints BE-003 (Tag State Management) and BE-004 (Bulk Operations).
- Create test cases for creating, updating, and bulk operations on cases and tags.
- Add a test for module deactivation to ensure data integrity is maintained.
- Include setup and teardown for tests to clear database state before and after each test.
2026-01-31 23:16:24 +01:00

373 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "shared/frontend/base.html" %}
{% block title %}Hardware - BMC Hub{% endblock %}
{% block extra_css %}
<style>
.page-header {
margin-bottom: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.page-header h1 {
font-size: 2rem;
font-weight: 700;
margin: 0;
}
.btn-new-hardware {
background-color: var(--accent);
color: white;
border: none;
padding: 0.6rem 1.5rem;
border-radius: 8px;
font-weight: 500;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-new-hardware:hover {
background-color: #0056b3;
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(15, 76, 117, 0.3);
}
.filter-section {
background: var(--bg-card);
padding: 1.5rem;
border-radius: 12px;
margin-bottom: 2rem;
border: 1px solid rgba(0,0,0,0.1);
}
.filter-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
align-items: end;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-group label {
font-weight: 500;
font-size: 0.9rem;
color: var(--text-secondary);
}
.filter-group select,
.filter-group input {
padding: 0.5rem;
border: 1px solid rgba(0,0,0,0.2);
border-radius: 6px;
background: var(--bg-body);
color: var(--text-primary);
}
.btn-filter {
padding: 0.5rem 1rem;
background-color: var(--accent);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-filter:hover {
background-color: #0056b3;
}
.hardware-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
}
.hardware-card {
background: var(--bg-card);
border-radius: 12px;
padding: 1.5rem;
border: 1px solid rgba(0,0,0,0.1);
transition: all 0.3s ease;
cursor: pointer;
}
.hardware-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
.hardware-header {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
align-items: flex-start;
}
.hardware-icon {
font-size: 2.5rem;
flex-shrink: 0;
}
.hardware-info {
flex: 1;
min-width: 0;
}
.hardware-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: 0.25rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hardware-subtitle {
font-size: 0.9rem;
color: var(--text-secondary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hardware-details {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
font-size: 0.9rem;
}
.hardware-detail-row {
display: flex;
justify-content: space-between;
gap: 0.5rem;
}
.hardware-detail-label {
color: var(--text-secondary);
font-weight: 500;
}
.hardware-detail-value {
color: var(--text-primary);
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.hardware-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid rgba(0,0,0,0.1);
}
.status-badge {
padding: 0.3rem 0.8rem;
border-radius: 6px;
font-size: 0.85rem;
font-weight: 500;
display: inline-block;
}
.status-active { background-color: #28a745; color: white; }
.status-faulty_reported { background-color: #ffc107; color: #000; }
.status-in_repair { background-color: #007bff; color: white; }
.status-replaced { background-color: #6f42c1; color: white; }
.status-retired { background-color: #6c757d; color: white; }
.status-unsupported { background-color: #dc3545; color: white; }
.hardware-actions {
display: flex;
gap: 0.5rem;
}
.btn-action {
padding: 0.4rem 0.8rem;
border-radius: 6px;
font-size: 0.85rem;
text-decoration: none;
transition: all 0.3s ease;
border: 1px solid rgba(0,0,0,0.2);
background: var(--bg-body);
color: var(--text-primary);
}
.btn-action:hover {
background-color: var(--accent);
color: white;
border-color: var(--accent);
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
color: var(--text-secondary);
}
.empty-state-icon {
font-size: 4rem;
margin-bottom: 1rem;
opacity: 0.3;
}
@media (max-width: 768px) {
.hardware-grid {
grid-template-columns: 1fr;
}
.filter-grid {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<div class="page-header">
<h1>🖥️ Hardware Assets</h1>
<a href="/hardware/new" class="btn-new-hardware">
Nyt Hardware
</a>
</div>
<div class="filter-section">
<form method="get" action="/hardware">
<div class="filter-grid">
<div class="filter-group">
<label for="asset_type">Type</label>
<select name="asset_type" id="asset_type">
<option value="">Alle typer</option>
<option value="pc" {% if current_asset_type == 'pc' %}selected{% endif %}>🖥️ PC</option>
<option value="laptop" {% if current_asset_type == 'laptop' %}selected{% endif %}>💻 Laptop</option>
<option value="printer" {% if current_asset_type == 'printer' %}selected{% endif %}>🖨️ Printer</option>
<option value="skærm" {% if current_asset_type == 'skærm' %}selected{% endif %}>🖥️ Skærm</option>
<option value="telefon" {% if current_asset_type == 'telefon' %}selected{% endif %}>📱 Telefon</option>
<option value="server" {% if current_asset_type == 'server' %}selected{% endif %}>🗄️ Server</option>
<option value="netværk" {% if current_asset_type == 'netværk' %}selected{% endif %}>🌐 Netværk</option>
<option value="andet" {% if current_asset_type == 'andet' %}selected{% endif %}>📦 Andet</option>
</select>
</div>
<div class="filter-group">
<label for="status">Status</label>
<select name="status" id="status">
<option value="">Alle status</option>
<option value="active" {% if current_status == 'active' %}selected{% endif %}>✅ Aktiv</option>
<option value="faulty_reported" {% if current_status == 'faulty_reported' %}selected{% endif %}>⚠️ Fejl rapporteret</option>
<option value="in_repair" {% if current_status == 'in_repair' %}selected{% endif %}>🔧 Under reparation</option>
<option value="replaced" {% if current_status == 'replaced' %}selected{% endif %}>🔄 Udskiftet</option>
<option value="retired" {% if current_status == 'retired' %}selected{% endif %}>📦 Udtjent</option>
<option value="unsupported" {% if current_status == 'unsupported' %}selected{% endif %}>❌ Ikke supporteret</option>
</select>
</div>
<div class="filter-group">
<label for="q">Søg</label>
<input type="text" name="q" id="q" placeholder="Serial, model, mærke..." value="{{ search_query or '' }}">
</div>
<div class="filter-group">
<label>&nbsp;</label>
<button type="submit" class="btn-filter">🔍 Filtrer</button>
</div>
</div>
</form>
</div>
{% if hardware and hardware|length > 0 %}
<div class="hardware-grid">
{% for item in hardware %}
<div class="hardware-card" onclick="window.location.href='/hardware/{{ item.id }}'">
<div class="hardware-header">
<div class="hardware-icon">
{% if item.asset_type == 'pc' %}🖥️
{% elif item.asset_type == 'laptop' %}💻
{% elif item.asset_type == 'printer' %}🖨️
{% elif item.asset_type == 'skærm' %}🖥️
{% elif item.asset_type == 'telefon' %}📱
{% elif item.asset_type == 'server' %}🗄️
{% elif item.asset_type == 'netværk' %}🌐
{% else %}📦
{% endif %}
</div>
<div class="hardware-info">
<div class="hardware-title">{{ item.brand or 'Unknown' }} {{ item.model or '' }}</div>
<div class="hardware-subtitle">{{ item.serial_number or 'Ingen serienummer' }}</div>
</div>
</div>
<div class="hardware-details">
<div class="hardware-detail-row">
<span class="hardware-detail-label">Type:</span>
<span class="hardware-detail-value">{{ item.asset_type|title }}</span>
</div>
{% if item.customer_name %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">Ejer:</span>
<span class="hardware-detail-value">{{ item.customer_name }}</span>
</div>
{% elif item.current_owner_type %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">Ejer:</span>
<span class="hardware-detail-value">{{ item.current_owner_type|title }}</span>
</div>
{% endif %}
{% if item.internal_asset_id %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">Asset ID:</span>
<span class="hardware-detail-value">{{ item.internal_asset_id }}</span>
</div>
{% endif %}
</div>
<div class="hardware-footer">
<span class="status-badge status-{{ item.status }}">
{{ item.status|replace('_', ' ')|title }}
</span>
<div class="hardware-actions" onclick="event.stopPropagation()">
<a href="/hardware/{{ item.id }}" class="btn-action">👁️ Se</a>
<a href="/hardware/{{ item.id }}/edit" class="btn-action">✏️ Rediger</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="empty-state">
<div class="empty-state-icon">🖥️</div>
<h3>Ingen hardware fundet</h3>
<p>Opret dit første hardware asset for at komme i gang.</p>
<a href="/hardware/new" class="btn-new-hardware" style="margin-top: 1rem;"> Opret Hardware</a>
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
<script>
// Auto-submit filter form on change
document.querySelectorAll('#asset_type, #status').forEach(select => {
select.addEventListener('change', () => {
select.form.submit();
});
});
</script>
{% endblock %}