298 lines
22 KiB
HTML
298 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{ device.name }} - Device Details</title>
|
|
<link rel="icon" type="image/png" href="{{ LOGO_PNG }}">
|
|
<link href="/static/css/output.css" rel="stylesheet">
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
|
|
</head>
|
|
<body class="bg-gray-300 text-gray-900 dark:bg-zinc-900 dark:text-gray-100 min-h-screen flex flex-col">
|
|
{% include 'header.html' %}
|
|
<div class="flex-1 flex items-center justify-center mx-4">
|
|
<div class="container py-8 max-w-2xl pt-20">
|
|
<div class="flex items-center mb-8 relative justify-between gap-4">
|
|
<a href="/devices" class="hidden sm:flex bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 flex items-center justify-center rounded-full w-11 h-11 shrink-0"><i class="fas fa-arrow-left"></i></a>
|
|
<h1 class="text-3xl font-bold text-center flex-1 min-w-0 truncate">{{ device.name }}</h1>
|
|
<form action="/update_device_type" method="POST" class="hidden md:inline ml-2">
|
|
<input type="hidden" name="device_id" value="{{ device.id }}">
|
|
<select name="device_type_id" class="border p-2 rounded bg-gray-200 dark:bg-zinc-800 border-gray-600" onchange="this.form.submit()">
|
|
{% for dtype in device_types %}
|
|
<option value="{{ dtype[0] }}" {% if device.device_type_id == dtype[0] %}selected{% endif %}>{{ dtype[1] }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</form>
|
|
<div class="flex items-center shrink-0">
|
|
<form action="/rename_device" method="POST" class="inline">
|
|
<input type="hidden" name="device_id" value="{{ device.id }}">
|
|
<input type="text" name="new_name" value="{{ device.name }}" class="hidden border p-1 rounded bg-gray-200 dark:bg-zinc-800 border-gray-600 w-32 mr-2" style="vertical-align: middle;" required>
|
|
<button type="button" class="text-blue-400 hover:text-blue-600 hover:cursor-pointer ml-2 rename-btn" title="Rename Device"><i class="fas fa-pencil-alt"></i></button>
|
|
<button type="submit" class="text-green-400 hover:text-green-600 hover:cursor-pointer ml-2 save-btn hidden" title="Save Name"><i class="fas fa-check"></i></button>
|
|
<button type="button" class="text-gray-400 hover:text-gray-600 hover:cursor-pointer ml-2 cancel-btn hidden" title="Cancel"><i class="fas fa-times"></i></button>
|
|
</form>
|
|
<form action="/delete_device" method="POST" onsubmit="return confirm('Are you sure you want to delete this device?');">
|
|
<input type="hidden" name="device_id" value="{{ device.id }}">
|
|
<button type="submit" class="ml-4 text-red-500 hover:text-red-700 hover:cursor-pointer" title="Delete Device">
|
|
<i class="fas fa-trash fa-lg"></i>
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<form action="/device/{{ device.id }}/add_ip" method="POST" class="mb-6">
|
|
<div class="flex flex-col space-y-4">
|
|
<select name="site" id="site-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full" required>
|
|
<option value="" disabled selected>Select Site...</option>
|
|
{% set sites = subnets | map(attribute='site') | unique | list %}
|
|
{% for site in sites %}
|
|
<option value="{{ site }}">{{ site }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
<select name="subnet_id" id="subnet-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full" required>
|
|
<option value="" disabled selected>Select Subnet...</option>
|
|
{% for subnet in subnets %}
|
|
<option value="{{ subnet.id }}" data-site="{{ subnet.site }}">{{ subnet.name }} ({{ subnet.cidr }})</option>
|
|
{% endfor %}
|
|
</select>
|
|
<select name="ip_id" id="ip-select" class="border p-3 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full" required>
|
|
<option value="" disabled selected>Select IP...</option>
|
|
</select>
|
|
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 hover:cursor-pointer px-4 py-2 rounded-lg w-full">Add IP</button>
|
|
</div>
|
|
</form>
|
|
<div class="allocated-ips mb-6">
|
|
<h3 class="text-lg font-bold mb-2">Allocated IPs:</h3>
|
|
<ul class="space-y-2">
|
|
{% for ip in device_ips %}
|
|
<li class="flex justify-between items-center bg-gray-200 dark:bg-zinc-700 p-2 rounded-lg">
|
|
<span class="allocated-ip">{{ ip.ip }}</span>
|
|
<form action="/device/{{ device.id }}/delete_ip" method="POST" class="inline">
|
|
<input type="hidden" name="device_ip_id" value="{{ ip.device_ip_id }}">
|
|
<button type="submit" class="text-red-500 hover:text-red-600 hover:cursor-pointer py-1 mr-2 text-lg"><i class="fas fa-trash"></i></button>
|
|
</form>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- IP History Section -->
|
|
{% if ip_history %}
|
|
<div class="ip-history mb-6">
|
|
<h3 class="text-lg font-bold mb-2">IP Assignment History:</h3>
|
|
<div class="bg-gray-200 dark:bg-zinc-700 rounded-lg p-4 max-h-96 overflow-y-auto">
|
|
<div class="space-y-3">
|
|
{% for entry in ip_history %}
|
|
<div class="flex items-start gap-3 pb-3 {% if not loop.last %}border-b border-gray-400 dark:border-zinc-600{% endif %}">
|
|
<div class="flex-shrink-0 mt-1">
|
|
{% if entry.action == 'assigned' %}
|
|
<i class="fas fa-plus-circle text-green-500"></i>
|
|
{% else %}
|
|
<i class="fas fa-minus-circle text-red-500"></i>
|
|
{% endif %}
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-2 flex-wrap">
|
|
<span class="font-mono font-semibold">{{ entry.ip }}</span>
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">
|
|
{% if entry.action == 'assigned' %}Assigned{% else %}Removed{% endif %}
|
|
</span>
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">
|
|
to {{ entry.device_name }}
|
|
</span>
|
|
</div>
|
|
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
|
{{ entry.subnet_name }} ({{ entry.subnet_cidr }})
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-500 mt-1">
|
|
by {{ entry.user_name }} • {{ entry.timestamp.strftime('%Y-%m-%d %H:%M:%S') if entry.timestamp else 'Unknown' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Tags Section -->
|
|
<div class="tags-section mb-6">
|
|
<h3 class="text-lg font-bold mb-2">Tags:</h3>
|
|
<div class="flex flex-wrap gap-2 mb-4">
|
|
{% if device_tags %}
|
|
{% for tag in device_tags %}
|
|
<div class="flex items-center space-x-1 px-3 py-1 rounded-full text-sm" style="background-color: {{ tag.color }}20; border: 1px solid {{ tag.color }}">
|
|
<div class="w-2 h-2 rounded-full" style="background-color: {{ tag.color }}"></div>
|
|
<span>{{ tag.name }}</span>
|
|
{% if can_remove_device_tag %}
|
|
<form action="/device/{{ device.id }}/remove_tag" method="POST" class="inline">
|
|
<input type="hidden" name="tag_id" value="{{ tag.id }}">
|
|
<button type="submit" class="ml-1 text-red-500 hover:text-red-700 text-xs" title="Remove tag">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<span class="text-gray-500">No tags assigned</span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% if can_assign_device_tag and all_tags %}
|
|
<form action="/device/{{ device.id }}/assign_tag" method="POST" class="flex gap-2">
|
|
<select name="tag_id" class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 flex-1" required>
|
|
<option value="" disabled selected>Select a tag to assign...</option>
|
|
{% for tag in all_tags %}
|
|
{% set already_assigned = device_tags|selectattr('id', 'equalto', tag.id)|list|length > 0 %}
|
|
{% if not already_assigned %}
|
|
<option value="{{ tag.id }}">{{ tag.name }}</option>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</select>
|
|
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 hover:cursor-pointer px-4 py-2 rounded-lg">
|
|
<i class="fas fa-plus mr-1"></i>Assign Tag
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<form action="/update_device_description" method="POST" class="mb-6 mt-4">
|
|
<input type="hidden" name="device_id" value="{{ device.id }}">
|
|
<label for="description" class="block mb-2 text-lg font-bold">Description</label>
|
|
<textarea id="description" name="description" rows="3" class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full resize-y" placeholder="Enter device description...">{{ device.description or '' }}</textarea>
|
|
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 hover:cursor-pointer px-4 py-2 rounded-lg w-full mt-2">Save Description</button>
|
|
</form>
|
|
|
|
<!-- Custom Fields Section -->
|
|
{% if custom_fields %}
|
|
<div class="custom-fields-section mb-6">
|
|
<h3 class="text-lg font-bold mb-4">Custom Fields</h3>
|
|
{% if can_edit_device %}
|
|
<form action="/device/{{ device.id }}/update_custom_fields" method="POST" id="custom-fields-form">
|
|
<div class="space-y-4">
|
|
{% for field in custom_fields %}
|
|
<div class="custom-field-item">
|
|
<label for="custom_field_{{ field.field_key }}" class="block mb-1 text-sm font-medium">
|
|
{{ field.name }}
|
|
{% if field.required %}<span class="text-red-500">*</span>{% endif %}
|
|
{% if field.help_text %}
|
|
<span class="text-xs text-gray-500 dark:text-gray-400 ml-2" title="{{ field.help_text }}">
|
|
<i class="fas fa-info-circle"></i>
|
|
</span>
|
|
{% endif %}
|
|
</label>
|
|
{% if field.field_type == 'textarea' %}
|
|
<textarea name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full resize-y"
|
|
{% if field.required %}required{% endif %}
|
|
placeholder="{{ field.help_text or '' }}">{{ field.current_value or field.default_value or '' }}</textarea>
|
|
{% elif field.field_type == 'boolean' %}
|
|
<div class="flex items-center space-x-2">
|
|
<input type="checkbox" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
value="true"
|
|
{% if field.current_value or (not field.current_value and field.default_value == 'true') %}checked{% endif %}
|
|
class="w-4 h-4">
|
|
<label for="custom_field_{{ field.field_key }}" class="text-sm">Yes</label>
|
|
</div>
|
|
{% elif field.field_type == 'select' %}
|
|
{% set options = [] %}
|
|
{% if field.validation_rules and field.validation_rules.select_options %}
|
|
{% set options = field.validation_rules.select_options %}
|
|
{% endif %}
|
|
<select name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
{% if field.required %}required{% endif %}>
|
|
{% if not field.required %}
|
|
<option value="">-- None --</option>
|
|
{% endif %}
|
|
{% for option in options %}
|
|
<option value="{{ option }}" {% if field.current_value == option or (not field.current_value and field.default_value == option) %}selected{% endif %}>{{ option }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% elif field.field_type == 'date' %}
|
|
<input type="date" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
{% if field.required %}required{% endif %}>
|
|
{% elif field.field_type == 'datetime' %}
|
|
<input type="datetime-local" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
{% if field.required %}required{% endif %}>
|
|
{% elif field.field_type == 'number' %}
|
|
<input type="number" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
{% if field.required %}required{% endif %}
|
|
{% if field.validation_rules and field.validation_rules.min_value %}min="{{ field.validation_rules.min_value }}"{% endif %}
|
|
{% if field.validation_rules and field.validation_rules.max_value %}max="{{ field.validation_rules.max_value }}"{% endif %}>
|
|
{% elif field.field_type == 'decimal' %}
|
|
<input type="number" step="any" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
{% if field.required %}required{% endif %}
|
|
{% if field.validation_rules and field.validation_rules.min_value %}min="{{ field.validation_rules.min_value }}"{% endif %}
|
|
{% if field.validation_rules and field.validation_rules.max_value %}max="{{ field.validation_rules.max_value }}"{% endif %}>
|
|
{% elif field.field_type == 'ip_address' %}
|
|
<input type="text" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full font-mono"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
placeholder="192.168.1.1"
|
|
{% if field.required %}required{% endif %}>
|
|
{% elif field.field_type == 'email' %}
|
|
<input type="email" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
placeholder="user@example.com"
|
|
{% if field.required %}required{% endif %}>
|
|
{% elif field.field_type == 'url' %}
|
|
<input type="url" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
placeholder="https://example.com"
|
|
{% if field.required %}required{% endif %}>
|
|
{% else %}
|
|
<input type="text" name="custom_field_{{ field.field_key }}"
|
|
id="custom_field_{{ field.field_key }}"
|
|
class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full"
|
|
value="{{ field.current_value or field.default_value or '' }}"
|
|
placeholder="{{ field.help_text or '' }}"
|
|
{% if field.required %}required{% endif %}
|
|
{% if field.validation_rules and field.validation_rules.min_length %}minlength="{{ field.validation_rules.min_length }}"{% endif %}
|
|
{% if field.validation_rules and field.validation_rules.max_length %}maxlength="{{ field.validation_rules.max_length }}"{% endif %}>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
<button type="submit" class="bg-gray-200 hover:bg-gray-400 dark:bg-zinc-700 dark:hover:bg-zinc-600 hover:cursor-pointer px-4 py-2 rounded-lg w-full mt-4">Save Custom Fields</button>
|
|
</form>
|
|
{% else %}
|
|
<div class="space-y-4">
|
|
{% for field in custom_fields %}
|
|
<div class="custom-field-item">
|
|
<label class="block mb-1 text-sm font-medium">{{ field.name }}</label>
|
|
<div class="border p-2 rounded-lg bg-gray-200 dark:bg-zinc-800 border-gray-600 w-full">
|
|
{{ field.current_value or field.default_value or '-' }}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<script src="/static/js/device.min.js"></script>
|
|
</body>
|
|
</html>
|