373 lines
12 KiB
HTML
373 lines
12 KiB
HTML
|
|
{% 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> </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 %}
|