Merge pull request #7105 from coollabsio/light-mode-wtf

fix: light mode wtf
This commit is contained in:
Andras Bacsai 2025-11-04 21:16:37 +01:00 committed by GitHub
commit bc3deeed03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 235 additions and 230 deletions

View file

@ -124,7 +124,7 @@ @utility menu {
} }
@utility menu-item { @utility menu-item {
@apply flex gap-3 items-center px-2 py-1 w-full text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64; @apply flex gap-3 items-center px-2 py-1 w-full text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64 text-black dark:text-neutral-400;
} }
@utility menu-item-active { @utility menu-item-active {
@ -220,7 +220,7 @@ @utility title {
} }
@utility subtitle { @utility subtitle {
@apply pt-2 pb-9; @apply pt-2 pb-9 text-neutral-500 dark:text-neutral-400;
} }
@utility fullscreen { @utility fullscreen {

View file

@ -9,15 +9,16 @@
@auth @auth
<livewire:deployments-indicator /> <livewire:deployments-indicator />
<div x-data="{ <div x-data="{
open: false, open: false,
init() { init() {
this.pageWidth = localStorage.getItem('pageWidth'); this.pageWidth = localStorage.getItem('pageWidth');
if (!this.pageWidth) { if (!this.pageWidth) {
this.pageWidth = 'full'; this.pageWidth = 'full';
localStorage.setItem('pageWidth', 'full'); localStorage.setItem('pageWidth', 'full');
} }
} }
}" x-cloak class="mx-auto" :class="pageWidth === 'full' ? '' : 'max-w-7xl'"> }" x-cloak class="mx-auto text-neutral-800 dark:text-white"
:class="pageWidth === 'full' ? '' : 'max-w-7xl'">
<div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true"> <div class="relative z-50 lg:hidden" :class="open ? 'block' : 'hidden'" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-black/80" x-on:click="open = false"></div> <div class="fixed inset-0 bg-black/80" x-on:click="open = false"></div>
<div class="fixed inset-y-0 right-0 h-full flex"> <div class="fixed inset-y-0 right-0 h-full flex">
@ -45,16 +46,18 @@
</div> </div>
</div> </div>
<div class="sticky top-0 z-40 flex items-center justify-between px-4 py-4 gap-x-6 sm:px-6 lg:hidden bg-white/95 dark:bg-base/95 backdrop-blur-sm border-b border-neutral-300/50 dark:border-coolgray-200/50"> <div
class="sticky top-0 z-40 flex items-center justify-between px-4 py-4 gap-x-6 sm:px-6 lg:hidden bg-white/95 dark:bg-base/95 backdrop-blur-sm border-b border-neutral-300/50 dark:border-coolgray-200/50">
<div class="flex items-center gap-3 flex-shrink-0"> <div class="flex items-center gap-3 flex-shrink-0">
<a href="/" class="text-xl font-bold tracking-wide dark:text-white hover:opacity-80 transition-opacity">Coolify</a> <a href="/"
class="text-xl font-bold tracking-wide dark:text-white hover:opacity-80 transition-opacity">Coolify</a>
<livewire:switch-team /> <livewire:switch-team />
</div> </div>
<button type="button" class="-m-2.5 p-2.5 dark:text-warning" x-on:click="open = !open"> <button type="button" class="-m-2.5 p-2.5 dark:text-warning" x-on:click="open = !open">
<span class="sr-only">Open sidebar</span> <span class="sr-only">Open sidebar</span>
<svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24"> <svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> d="M4 6h16M4 12h16M4 18h16" />
</svg> </svg>
</button> </button>
</div> </div>

View file

@ -2,17 +2,19 @@
<html data-theme="dark" lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <html data-theme="dark" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<script> <script>
// Immediate theme application - runs before any rendering // Immediate theme application - runs before any rendering
(function() { (function () {
const t = localStorage.theme || 'dark'; const t = localStorage.theme || 'dark';
const d = t === 'dark' || (t === 'system' && matchMedia('(prefers-color-scheme: dark)').matches); const d = t === 'dark' || (t === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList[d ? 'add' : 'remove']('dark'); document.documentElement.classList[d ? 'add' : 'remove']('dark');
document.documentElement.setAttribute('data-theme', d ? 'dark' : 'light');
})(); })();
</script> </script>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex"> <meta name="robots" content="noindex">
<meta name="theme-color" content="#101010" id="theme-color-meta" /> <meta name="theme-color" content="#ffffff" id="theme-color-meta" />
<meta name="color-scheme" content="dark light" /> <meta name="color-scheme" content="dark light" />
<meta name="Description" content="Coolify: An open-source & self-hostable Heroku / Netlify / Vercel alternative" /> <meta name="Description" content="Coolify: An open-source & self-hostable Heroku / Netlify / Vercel alternative" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
@ -73,102 +75,102 @@
</head> </head>
@section('body') @section('body')
<body> <body>
<x-toast /> <x-toast />
<script data-navigate-once> <script data-navigate-once>
// Global HTML sanitization function using DOMPurify // Global HTML sanitization function using DOMPurify
window.sanitizeHTML = function(html) { window.sanitizeHTML = function (html) {
if (!html) return ''; if (!html) return '';
const URL_RE = /^(https?:|mailto:)/i; const URL_RE = /^(https?:|mailto:)/i;
const config = { const config = {
ALLOWED_TAGS: ['a', 'b', 'br', 'code', 'del', 'div', 'em', 'i', 'p', 'pre', 's', 'span', 'strong', ALLOWED_TAGS: ['a', 'b', 'br', 'code', 'del', 'div', 'em', 'i', 'p', 'pre', 's', 'span', 'strong',
'u' 'u'
], ],
ALLOWED_ATTR: ['class', 'href', 'target', 'title', 'rel'], ALLOWED_ATTR: ['class', 'href', 'target', 'title', 'rel'],
ALLOW_DATA_ATTR: false, ALLOW_DATA_ATTR: false,
FORBID_TAGS: ['script', 'object', 'embed', 'applet', 'iframe', 'form', 'input', 'button', 'select', FORBID_TAGS: ['script', 'object', 'embed', 'applet', 'iframe', 'form', 'input', 'button', 'select',
'textarea', 'details', 'summary', 'dialog', 'style' 'textarea', 'details', 'summary', 'dialog', 'style'
], ],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange', FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange',
'onsubmit', 'ontoggle', 'style' 'onsubmit', 'ontoggle', 'style'
], ],
KEEP_CONTENT: true, KEEP_CONTENT: true,
RETURN_DOM: false, RETURN_DOM: false,
RETURN_DOM_FRAGMENT: false, RETURN_DOM_FRAGMENT: false,
SANITIZE_DOM: true, SANITIZE_DOM: true,
SANITIZE_NAMED_PROPS: true, SANITIZE_NAMED_PROPS: true,
SAFE_FOR_TEMPLATES: true, SAFE_FOR_TEMPLATES: true,
ALLOWED_URI_REGEXP: URL_RE ALLOWED_URI_REGEXP: URL_RE
};
// One-time hook registration (idempotent pattern)
if (!window.__dpLinkHook) {
DOMPurify.addHook('afterSanitizeAttributes', node => {
// Remove Alpine.js directives to prevent XSS
if (node.hasAttributes && node.hasAttributes()) {
const attrs = Array.from(node.attributes);
attrs.forEach(attr => {
// Remove x-* attributes (Alpine directives)
if (attr.name.startsWith('x-')) {
node.removeAttribute(attr.name);
}
// Remove @* attributes (Alpine event shorthand)
if (attr.name.startsWith('@')) {
node.removeAttribute(attr.name);
}
// Remove :* attributes (Alpine binding shorthand)
if (attr.name.startsWith(':')) {
node.removeAttribute(attr.name);
}
});
}
// Existing link sanitization
if (node.nodeName === 'A' && node.hasAttribute('href')) {
const href = node.getAttribute('href') || '';
if (!URL_RE.test(href)) node.removeAttribute('href');
if (node.getAttribute('target') === '_blank') {
node.setAttribute('rel', 'noopener noreferrer');
}
}
});
window.__dpLinkHook = true;
}
return DOMPurify.sanitize(html, config);
}; };
// Initialize theme if not set // One-time hook registration (idempotent pattern)
if (!('theme' in localStorage)) { if (!window.__dpLinkHook) {
localStorage.theme = 'dark'; DOMPurify.addHook('afterSanitizeAttributes', node => {
} // Remove Alpine.js directives to prevent XSS
if (node.hasAttributes && node.hasAttributes()) {
const attrs = Array.from(node.attributes);
attrs.forEach(attr => {
// Remove x-* attributes (Alpine directives)
if (attr.name.startsWith('x-')) {
node.removeAttribute(attr.name);
}
// Remove @* attributes (Alpine event shorthand)
if (attr.name.startsWith('@')) {
node.removeAttribute(attr.name);
}
// Remove :* attributes (Alpine binding shorthand)
if (attr.name.startsWith(':')) {
node.removeAttribute(attr.name);
}
});
}
let theme = localStorage.theme // Existing link sanitization
let cpuColor = '#1e90ff' if (node.nodeName === 'A' && node.hasAttribute('href')) {
let ramColor = '#00ced1' const href = node.getAttribute('href') || '';
let textColor = '#ffffff' if (!URL_RE.test(href)) node.removeAttribute('href');
let editorBackground = '#181818' if (node.getAttribute('target') === '_blank') {
let editorTheme = 'blackboard' node.setAttribute('rel', 'noopener noreferrer');
}
function checkTheme() { }
theme = localStorage.theme });
if (theme == 'system') { window.__dpLinkHook = true;
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
if (theme == 'dark') {
cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#ffffff'
editorBackground = '#181818'
editorTheme = 'blackboard'
} else {
cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#000000'
editorBackground = '#ffffff'
editorTheme = null
}
} }
@auth return DOMPurify.sanitize(html, config);
};
// Initialize theme if not set
if (!('theme' in localStorage)) {
localStorage.theme = 'dark';
}
let theme = localStorage.theme
let cpuColor = '#1e90ff'
let ramColor = '#00ced1'
let textColor = '#ffffff'
let editorBackground = '#181818'
let editorTheme = 'blackboard'
function checkTheme() {
theme = localStorage.theme
if (theme == 'system') {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
if (theme == 'dark') {
cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#ffffff'
editorBackground = '#181818'
editorTheme = 'blackboard'
} else {
cpuColor = '#1e90ff'
ramColor = '#00ced1'
textColor = '#000000'
editorBackground = '#ffffff'
editorTheme = null
}
}
@auth
window.Pusher = Pusher; window.Pusher = Pusher;
window.Echo = new Echo({ window.Echo = new Echo({
broadcaster: 'pusher', broadcaster: 'pusher',
@ -197,131 +199,131 @@ function checkTheme() {
// Maximum number of reconnection attempts // Maximum number of reconnection attempts
maxAttempts: 15 maxAttempts: 15
}); });
@endauth @endauth
let checkHealthInterval = null; let checkHealthInterval = null;
let checkIfIamDeadInterval = null; let checkIfIamDeadInterval = null;
function changePasswordFieldType(event) { function changePasswordFieldType(event) {
let element = event.target let element = event.target
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
if (element.className === "relative") { if (element.className === "relative") {
break; break;
}
element = element.parentElement;
} }
element = element.children[1]; element = element.parentElement;
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') { }
if (element.type === 'password') { element = element.children[1];
element.type = 'text'; if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
if (element.disabled) return; if (element.type === 'password') {
element.classList.add('truncate'); element.type = 'text';
this.type = 'text'; if (element.disabled) return;
} else { element.classList.add('truncate');
element.type = 'password'; this.type = 'text';
if (element.disabled) return; } else {
element.classList.remove('truncate'); element.type = 'password';
this.type = 'password'; if (element.disabled) return;
} element.classList.remove('truncate');
this.type = 'password';
} }
} }
}
function copyToClipboard(text) { function copyToClipboard(text) {
navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.'); navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.');
} }
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
window.Livewire.on('reloadWindow', (timeout) => { window.Livewire.on('reloadWindow', (timeout) => {
if (timeout) { if (timeout) {
setTimeout(() => { setTimeout(() => {
window.location.reload();
}, timeout);
return;
} else {
window.location.reload(); window.location.reload();
} }, timeout);
}) return;
window.Livewire.on('info', (message) => { } else {
if (typeof message === 'string') { window.location.reload();
window.toast('Info', { }
type: 'info', })
description: message, window.Livewire.on('info', (message) => {
}) if (typeof message === 'string') {
return; window.toast('Info', {
} type: 'info',
if (message.length == 1) { description: message,
window.toast('Info', { })
type: 'info', return;
description: message[0], }
}) if (message.length == 1) {
} else if (message.length == 2) { window.toast('Info', {
window.toast(message[0], { type: 'info',
type: 'info', description: message[0],
description: message[1], })
}) } else if (message.length == 2) {
} window.toast(message[0], {
}) type: 'info',
window.Livewire.on('error', (message) => { description: message[1],
if (typeof message === 'string') { })
window.toast('Error', { }
type: 'danger', })
description: message, window.Livewire.on('error', (message) => {
}) if (typeof message === 'string') {
return; window.toast('Error', {
} type: 'danger',
if (message.length == 1) { description: message,
window.toast('Error', { })
type: 'danger', return;
description: message[0], }
}) if (message.length == 1) {
} else if (message.length == 2) { window.toast('Error', {
window.toast(message[0], { type: 'danger',
type: 'danger', description: message[0],
description: message[1], })
}) } else if (message.length == 2) {
} window.toast(message[0], {
}) type: 'danger',
window.Livewire.on('warning', (message) => { description: message[1],
if (typeof message === 'string') { })
window.toast('Warning', { }
type: 'warning', })
description: message, window.Livewire.on('warning', (message) => {
}) if (typeof message === 'string') {
return; window.toast('Warning', {
} type: 'warning',
if (message.length == 1) { description: message,
window.toast('Warning', { })
type: 'warning', return;
description: message[0], }
}) if (message.length == 1) {
} else if (message.length == 2) { window.toast('Warning', {
window.toast(message[0], { type: 'warning',
type: 'warning', description: message[0],
description: message[1], })
}) } else if (message.length == 2) {
} window.toast(message[0], {
}) type: 'warning',
window.Livewire.on('success', (message) => { description: message[1],
if (typeof message === 'string') { })
window.toast('Success', { }
type: 'success', })
description: message, window.Livewire.on('success', (message) => {
}) if (typeof message === 'string') {
return; window.toast('Success', {
} type: 'success',
if (message.length == 1) { description: message,
window.toast('Success', { })
type: 'success', return;
description: message[0], }
}) if (message.length == 1) {
} else if (message.length == 2) { window.toast('Success', {
window.toast(message[0], { type: 'success',
type: 'success', description: message[0],
description: message[1], })
}) } else if (message.length == 2) {
} window.toast(message[0], {
}) type: 'success',
}); description: message[1],
</script> })
</body> }
})
});
</script>
</body>
@show @show
</html> </html>