bmc_hub/app/modules/hardware/templates/index.html

466 lines
16 KiB
HTML
Raw Normal View History

{% 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 Oversigt</h1>
<div class="d-flex gap-2">
<a href="/hardware/eset" class="btn-new-hardware" style="background-color: #0f4c75;">
<i class="bi bi-shield-check"></i>
ESET Oversigt
</a>
<a href="/hardware/new" class="btn-new-hardware">
Nyt Hardware
</a>
</div>
</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="d-flex justify-content-end mb-3">
<div class="btn-group btn-group-sm" role="group" aria-label="Visning">
<button type="button" class="btn btn-outline-secondary active" id="viewCardsBtn" onclick="setHardwareView('cards')">Kort</button>
<button type="button" class="btn btn-outline-secondary" id="viewTableBtn" onclick="setHardwareView('table')">Tabel</button>
</div>
</div>
<div id="hardwareCardsView">
<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.anydesk_id or item.anydesk_link %}
<div class="hardware-detail-row">
<span class="hardware-detail-label">AnyDesk:</span>
<span class="hardware-detail-value">
{% if item.anydesk_link %}
<a href="{{ item.anydesk_link }}" target="_blank">{{ item.anydesk_id or 'Åbn' }}</a>
{% elif item.anydesk_id %}
<a href="anydesk://{{ item.anydesk_id }}" target="_blank">{{ item.anydesk_id }}</a>
{% endif %}
</span>
</div>
{% endif %}
{% 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>
</div>
<div id="hardwareTableView" class="d-none">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>Hardware</th>
<th>Type</th>
<th>Serienr.</th>
<th>Ejer</th>
<th>Status</th>
<th>AnyDesk</th>
<th class="text-end">Handling</th>
</tr>
</thead>
<tbody>
{% for item in hardware %}
<tr>
<td class="fw-semibold">{{ item.brand or 'Unknown' }} {{ item.model or '' }}</td>
<td>{{ item.asset_type|title }}</td>
<td>{{ item.serial_number or 'Ingen serienummer' }}</td>
<td>{{ item.customer_name or (item.current_owner_type|title if item.current_owner_type else '—') }}</td>
<td>
<span class="status-badge status-{{ item.status }}">
{{ item.status|replace('_', ' ')|title }}
</span>
</td>
<td>
{% if item.anydesk_link %}
<a href="{{ item.anydesk_link }}" target="_blank">{{ item.anydesk_id or 'Åbn' }}</a>
{% elif item.anydesk_id %}
<a href="anydesk://{{ item.anydesk_id }}" target="_blank">{{ item.anydesk_id }}</a>
{% else %}
{% endif %}
</td>
<td class="text-end">
<a href="/hardware/{{ item.id }}" class="btn-action">👁️ Se</a>
<a href="/hardware/{{ item.id }}/edit" class="btn-action">✏️ Rediger</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</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();
});
});
function setHardwareView(view) {
const cards = document.getElementById('hardwareCardsView');
const table = document.getElementById('hardwareTableView');
const cardsBtn = document.getElementById('viewCardsBtn');
const tableBtn = document.getElementById('viewTableBtn');
if (!cards || !table || !cardsBtn || !tableBtn) return;
const showTable = view === 'table';
cards.classList.toggle('d-none', showTable);
table.classList.toggle('d-none', !showTable);
cardsBtn.classList.toggle('active', !showTable);
tableBtn.classList.toggle('active', showTable);
localStorage.setItem('hardwareViewMode', showTable ? 'table' : 'cards');
}
const savedView = localStorage.getItem('hardwareViewMode');
if (savedView === 'table') {
setHardwareView('table');
}
</script>
{% endblock %}