mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 20:17:05 +00:00
feat(dashboard): add server metrics charts and polish UI feedback
This commit is contained in:
1 parent
2949b8e158
commit
f412b2bf0d
24 files changed
+493
-104
No files matched your search
@@ -1,5 +1,6 @@
|
||||
<div x-data="{
|
||||
visible: false,
|
||||
positioned: false,
|
||||
text: '',
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -7,7 +8,10 @@
|
||||
activeTarget: null,
|
||||
isIconAction(target) {
|
||||
if (target.matches('[data-icon-tooltip-ignore]')) return false;
|
||||
return target.matches('[data-tooltip], .icon-button') || target.querySelector('svg');
|
||||
if (target.matches('[data-tooltip], .icon-button')) return true;
|
||||
return target.hasAttribute('aria-label')
|
||||
&& target.childElementCount === 1
|
||||
&& target.firstElementChild?.matches('svg');
|
||||
},
|
||||
prepare(root) {
|
||||
root.querySelectorAll?.('button[title], a[title], [data-tooltip]').forEach((target) => {
|
||||
@@ -25,10 +29,12 @@
|
||||
show(event) {
|
||||
const target = this.findTarget(event);
|
||||
if (!target) return;
|
||||
if (target === this.activeTarget && this.visible) return;
|
||||
const text = target.dataset.tooltip || target.dataset.iconTooltip || target.getAttribute('aria-label');
|
||||
if (!text) return;
|
||||
this.activeTarget = target;
|
||||
this.text = text;
|
||||
this.positioned = false;
|
||||
this.visible = true;
|
||||
const rect = target.getBoundingClientRect();
|
||||
this.below = rect.top < 48;
|
||||
@@ -37,11 +43,13 @@
|
||||
this.$nextTick(() => {
|
||||
const width = this.$refs.tooltip?.offsetWidth || 0;
|
||||
this.x = Math.max(width / 2 + 8, Math.min(window.innerWidth - width / 2 - 8, this.x));
|
||||
this.$nextTick(() => this.positioned = true);
|
||||
});
|
||||
},
|
||||
hide(event) {
|
||||
if (event?.relatedTarget && this.activeTarget?.contains(event.relatedTarget)) return;
|
||||
this.visible = false;
|
||||
this.positioned = false;
|
||||
this.activeTarget = null;
|
||||
},
|
||||
init() {
|
||||
@@ -62,7 +70,7 @@
|
||||
}" class="contents">
|
||||
<div x-ref="tooltip" x-show="visible" x-cloak role="tooltip" x-text="text"
|
||||
:style="`left: ${x}px; top: ${y}px;`"
|
||||
:class="below ? '' : '-translate-y-full'"
|
||||
:class="[below ? '' : '-translate-y-full', positioned ? 'visible' : 'invisible']"
|
||||
class="pointer-events-none fixed z-[100] -translate-x-1/2 whitespace-nowrap rounded-lg border border-neutral-700 bg-neutral-900 px-2 py-1 text-xs font-medium text-white shadow-lg dark:border-white/10 dark:bg-raised">
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,7 +67,7 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<ul role="list" class="flex min-h-0 flex-1 flex-col gap-y-0.5 overflow-y-auto pb-2 scrollbar">
|
||||
<ul role="list" class="-mx-1 flex min-h-0 flex-1 flex-col gap-y-0.5 overflow-y-auto px-1 pb-2 scrollbar">
|
||||
@if (isSubscribed() || !isCloud())
|
||||
{{-- Workspace --}}
|
||||
<li class="nav-section" :class="collapsed && 'lg:hidden'">Workspace</li>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
appearanceOpen: false,
|
||||
theme: localStorage.getItem('theme') === 'purple' ? 'custom' : (localStorage.getItem('theme') || 'dark'),
|
||||
themeColor: localStorage.getItem('themeColor') || '#6b16ed',
|
||||
themeColorFrame: null,
|
||||
avatarUrl: @js($user?->avatar_path ? route('profile.avatar', ['v' => $user->updated_at->timestamp]) : null),
|
||||
setTheme(type, closeMenu = true) {
|
||||
this.theme = type;
|
||||
@@ -31,9 +32,29 @@
|
||||
document.documentElement.style.setProperty('--theme-accent-foreground', window.themeAccentForeground(this.themeColor));
|
||||
document.querySelector('meta[name=theme-color]')?.setAttribute('content', isDark ? '#101010' : '#ffffff');
|
||||
},
|
||||
setThemeColor() {
|
||||
localStorage.setItem('themeColor', this.themeColor);
|
||||
this.setTheme('custom', false);
|
||||
previewThemeColor(color) {
|
||||
this.themeColor = color;
|
||||
|
||||
if (this.theme !== 'custom') {
|
||||
this.theme = 'custom';
|
||||
document.documentElement.classList.add('dark');
|
||||
document.documentElement.dataset.theme = 'custom';
|
||||
}
|
||||
|
||||
if (this.themeColorFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.themeColorFrame = requestAnimationFrame(() => {
|
||||
document.documentElement.style.setProperty('--theme-base-color', this.themeColor);
|
||||
document.documentElement.style.setProperty('--theme-accent-foreground', window.themeAccentForeground(this.themeColor));
|
||||
this.themeColorFrame = null;
|
||||
});
|
||||
},
|
||||
saveThemeColor(color) {
|
||||
this.previewThemeColor(color);
|
||||
localStorage.setItem('themeColor', color);
|
||||
localStorage.setItem('theme', 'custom');
|
||||
},
|
||||
}" @avatar-updated.window="avatarUrl = $event.detail.url" @keydown.escape.window="open = false; appearanceOpen = false"
|
||||
@click.outside="open = false; appearanceOpen = false">
|
||||
@@ -62,9 +83,11 @@
|
||||
|
||||
<template x-if="open">
|
||||
<div @class([
|
||||
'listbox-panel z-[90]! max-h-none! w-52! min-w-0! overflow-visible!',
|
||||
'listbox-panel z-[90]! max-h-none! w-52! min-w-0! overflow-visible! animate-in fade-in zoom-in-95 duration-150',
|
||||
'right-0! left-auto!' => ! $sidebar,
|
||||
'bottom-full! left-0! right-auto! top-auto! mb-1!' => $sidebar,
|
||||
'origin-bottom-left' => $sidebar,
|
||||
'origin-top-right' => ! $sidebar,
|
||||
])>
|
||||
<div class="min-w-0 px-2 py-1.5">
|
||||
<div class="truncate text-[13px] font-semibold text-black dark:text-fg">{{ $userName }}</div>
|
||||
@@ -110,7 +133,8 @@
|
||||
<path d="m2.5 6.25 2.1 2.1 4.9-5" stroke="currentColor" stroke-width="1.4"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
<input type="color" x-model="themeColor" @input="setThemeColor()"
|
||||
<input type="color" :value="themeColor" @input="previewThemeColor($event.target.value)"
|
||||
@change="saveThemeColor($event.target.value)"
|
||||
aria-label="Custom theme color"
|
||||
class="absolute inset-0 z-10 h-full w-full cursor-pointer opacity-0" />
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user