feat: add server icon customization modal with icon and color selection

This commit is contained in:
2025-12-06 10:51:27 -05:00
parent faea94bc60
commit 1d179e980b

View File

@@ -1692,3 +1692,213 @@
</div>
@endif
</div>
<!-- Server Icon Customization Modal -->
<div id="serverIconModal" style="display: none;" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
<div class="bg-white dark:bg-background-secondary rounded-xl shadow-2xl p-6 w-full max-w-md relative">
<button onclick="closeServerIconModal()" class="absolute top-3 right-3 text-base/50 hover:text-base transition-colors">
<x-ri-close-line class="size-5" />
</button>
<h3 class="text-lg font-semibold mb-4">Customize Server Icon</h3>
<div class="flex items-center gap-4 mb-4">
<div id="iconPreview" class="size-12 rounded-xl border flex items-center justify-center text-3xl">
<i id="iconPreviewIcon" class="ri-server-line text-base/40"></i>
</div>
<input id="iconSearch" type="text" placeholder="Search icons..." class="border rounded px-2 py-1 text-sm w-40 dark:bg-background dark:border-base/20" oninput="filterIcons()" />
</div>
<div id="iconGrid" class="grid grid-cols-6 gap-2 mb-4 max-h-32 overflow-y-auto">
@php
$availableIcons = [
'server-line', 'database-2-line', 'computer-line', 'cpu-line', 'hard-drive-2-line',
'cloud-line', 'terminal-box-line', 'code-s-slash-line', 'braces-line',
'global-line', 'earth-line', 'wifi-line', 'signal-tower-line',
'shield-check-line', 'shield-keyhole-line', 'lock-line', 'lock-unlock-line', 'key-2-line',
'rocket-line', 'rocket-2-line', 'send-plane-line', 'flashlight-line', 'fire-line',
'sparkling-line', 'star-line', 'heart-line', 'home-line', 'home-2-line',
'building-line', 'building-2-line', 'store-line', 'box-3-line', 'archive-line',
'inbox-line', 'inbox-2-line', 'folder-line', 'folder-open-line',
'settings-3-line', 'settings-4-line', 'tools-line', 'hammer-line',
'flask-line', 'bug-line', 'puzzle-line', 'bar-chart-box-line', 'pie-chart-line',
'line-chart-line', 'money-dollar-circle-line', 'money-euro-circle-line',
'bank-card-line', 'wallet-line', 'user-line', 'user-3-line', 'group-line', 'team-line',
'mail-line', 'mail-open-line', 'chat-3-line', 'discuss-line',
'camera-line', 'vidicon-line', 'film-line', 'music-line',
'book-open-line', 'file-text-line', 'sun-line', 'moon-line',
'palette-line', 'brush-line', 'trophy-line', 'graduation-cap-line',
'lightbulb-line', 'fingerprint-line', 'eye-line', 'map-2-line', 'map-pin-line',
'time-line', 'calendar-line', 'gift-line', 'cake-2-line',
'gamepad-line', 'ghost-line', 'aliens-line', 'space-ship-line',
'android-line', 'apple-line', 'windows-line', 'ubuntu-line',
'github-line', 'gitlab-line', 'discord-line', 'slack-line'
];
@endphp
@foreach($availableIcons as $iconName)
<button onclick="selectIcon('{{ $iconName }}')" data-icon="{{ $iconName }}" class="icon-button rounded p-1 border flex items-center justify-center hover:bg-secondary/10 text-xl transition-all dark:border-base/20">
<i class="ri-{{ $iconName }}"></i>
</button>
@endforeach
</div>
<div class="flex flex-wrap gap-2 mb-4">
@php
$availableColors = [
'' => 'Default',
'#ef4444' => 'Red',
'#f97316' => 'Orange',
'#f59e0b' => 'Amber',
'#eab308' => 'Yellow',
'#84cc16' => 'Lime',
'#22c55e' => 'Green',
'#10b981' => 'Emerald',
'#14b8a6' => 'Teal',
'#06b6d4' => 'Cyan',
'#0ea5e9' => 'Sky',
'#3b82f6' => 'Blue',
'#6366f1' => 'Indigo',
'#8b5cf6' => 'Violet',
'#a855f7' => 'Purple',
'#d946ef' => 'Fuchsia',
'#ec4899' => 'Pink',
'#f43f5e' => 'Rose',
'#78716c' => 'Stone',
'#64748b' => 'Slate'
];
@endphp
@foreach($availableColors as $colorValue => $colorName)
<button onclick="selectColor('{{ $colorValue }}')" data-color="{{ $colorValue }}" title="{{ $colorName }}" class="color-button size-6 rounded-full border transition-all" style="background: {{ $colorValue ?: '#e5e7eb' }}"></button>
@endforeach
</div>
<div class="flex gap-2 justify-end">
<button onclick="resetServerIcon()" class="text-xs text-base/60 hover:text-danger transition-colors">Reset</button>
<button onclick="saveServerIcon()" class="px-3 py-1 rounded bg-primary text-white text-xs hover:bg-primary/80 transition-colors">Save</button>
</div>
<div id="iconMessage" class="mt-2 text-xs text-success hidden"></div>
</div>
</div>
<script>
let serverIconModalData = {
serverId: null,
selectedIcon: '',
selectedColor: ''
};
function openServerIconModal(serverId, currentIcon, currentColor) {
serverIconModalData.serverId = serverId;
serverIconModalData.selectedIcon = currentIcon || '';
serverIconModalData.selectedColor = currentColor || '';
updateIconPreview();
updateIconSelection();
updateColorSelection();
document.getElementById('serverIconModal').style.display = 'flex';
document.getElementById('iconSearch').value = '';
filterIcons();
}
function closeServerIconModal() {
document.getElementById('serverIconModal').style.display = 'none';
document.getElementById('iconMessage').classList.add('hidden');
}
function selectIcon(iconName) {
serverIconModalData.selectedIcon = iconName;
updateIconPreview();
updateIconSelection();
}
function selectColor(color) {
serverIconModalData.selectedColor = color;
updateIconPreview();
updateColorSelection();
}
function resetServerIcon() {
serverIconModalData.selectedIcon = '';
serverIconModalData.selectedColor = '';
updateIconPreview();
updateIconSelection();
updateColorSelection();
}
function updateIconPreview() {
const preview = document.getElementById('iconPreview');
const previewIcon = document.getElementById('iconPreviewIcon');
if (serverIconModalData.selectedIcon) {
previewIcon.className = 'ri-' + serverIconModalData.selectedIcon;
previewIcon.style.color = serverIconModalData.selectedColor || '';
preview.style.borderColor = serverIconModalData.selectedColor || '';
} else {
previewIcon.className = 'ri-server-line text-base/40';
previewIcon.style.color = '';
preview.style.borderColor = '';
}
}
function updateIconSelection() {
document.querySelectorAll('.icon-button').forEach(btn => {
if (btn.dataset.icon === serverIconModalData.selectedIcon) {
btn.classList.add('ring-2', 'ring-primary');
} else {
btn.classList.remove('ring-2', 'ring-primary');
}
});
}
function updateColorSelection() {
document.querySelectorAll('.color-button').forEach(btn => {
if (btn.dataset.color === serverIconModalData.selectedColor) {
btn.classList.add('ring-2', 'ring-primary');
} else {
btn.classList.remove('ring-2', 'ring-primary');
}
});
}
function filterIcons() {
const search = document.getElementById('iconSearch').value.toLowerCase();
document.querySelectorAll('.icon-button').forEach(btn => {
const iconName = btn.dataset.icon;
if (iconName.includes(search)) {
btn.style.display = 'flex';
} else {
btn.style.display = 'none';
}
});
}
function saveServerIcon() {
fetch(`/proxmox/server/${serverIconModalData.serverId}/icon`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content
},
body: JSON.stringify({
icon: serverIconModalData.selectedIcon,
color: serverIconModalData.selectedColor
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const messageEl = document.getElementById('iconMessage');
messageEl.textContent = 'Icon saved!';
messageEl.classList.remove('hidden');
setTimeout(() => {
closeServerIconModal();
location.reload();
}, 800);
}
})
.catch(error => {
console.error('Error saving icon:', error);
});
}
</script>