feat: add server icon customization modal with icon and color selection
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user