2023-08-29 14:31:46 +00:00
|
|
|
<!DOCTYPE html>
|
2024-03-24 15:00:25 +00:00
|
|
|
<html data-theme="dark" lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
2025-10-23 18:46:58 +00:00
|
|
|
<script>
|
|
|
|
|
// Immediate theme application - runs before any rendering
|
2025-11-06 07:43:43 +00:00
|
|
|
(function () {
|
2025-10-23 18:46:58 +00:00
|
|
|
const t = localStorage.theme || 'dark';
|
|
|
|
|
const d = t === 'dark' || (t === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
|
|
|
document.documentElement.classList[d ? 'add' : 'remove']('dark');
|
2025-11-04 20:15:59 +00:00
|
|
|
document.documentElement.setAttribute('data-theme', d ? 'dark' : 'light');
|
2025-10-23 18:46:58 +00:00
|
|
|
})();
|
|
|
|
|
</script>
|
2025-11-04 20:15:59 +00:00
|
|
|
|
2023-08-29 14:31:46 +00:00
|
|
|
<head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
2023-10-17 11:28:33 +00:00
|
|
|
<meta name="robots" content="noindex">
|
2025-11-04 20:15:59 +00:00
|
|
|
<meta name="theme-color" content="#ffffff" id="theme-color-meta" />
|
2025-10-23 18:46:58 +00:00
|
|
|
<meta name="color-scheme" content="dark light" />
|
2026-02-16 01:54:19 +00:00
|
|
|
{{-- MapleDeploy branding --}}
|
|
|
|
|
<meta name="Description" content="MapleDeploy: Managed Coolify hosting on Canadian infrastructure" />
|
2025-02-02 13:20:41 +00:00
|
|
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
|
|
|
<meta name="twitter:card" content="summary_large_image" />
|
2026-02-16 01:54:19 +00:00
|
|
|
<meta name="twitter:title" content="MapleDeploy" />
|
|
|
|
|
<meta name="twitter:description" content="Managed Coolify hosting on Canadian infrastructure." />
|
2025-02-02 13:20:41 +00:00
|
|
|
<meta property="og:type" content="website" />
|
2026-02-16 01:54:19 +00:00
|
|
|
<meta property="og:url" content="https://mapledeploy.ca" />
|
|
|
|
|
<meta property="og:title" content="MapleDeploy" />
|
|
|
|
|
<meta property="og:description" content="Managed Coolify hosting on Canadian infrastructure." />
|
|
|
|
|
<meta property="og:site_name" content="MapleDeploy" />
|
2024-06-22 08:22:57 +00:00
|
|
|
@use('App\Models\InstanceSettings')
|
|
|
|
|
@php
|
|
|
|
|
|
2024-10-01 08:37:40 +00:00
|
|
|
$instanceSettings = instanceSettings();
|
2024-06-25 11:35:58 +00:00
|
|
|
$name = null;
|
2024-06-22 08:30:40 +00:00
|
|
|
|
2024-06-25 11:35:58 +00:00
|
|
|
if ($instanceSettings) {
|
|
|
|
|
$displayName = $instanceSettings->getTitleDisplayName();
|
2024-06-24 09:16:47 +00:00
|
|
|
|
2024-06-25 11:35:58 +00:00
|
|
|
if (strlen($displayName) > 0) {
|
|
|
|
|
$name = $displayName . ' ';
|
|
|
|
|
}
|
2024-06-22 08:30:40 +00:00
|
|
|
}
|
2024-06-22 08:22:57 +00:00
|
|
|
@endphp
|
2026-02-16 01:54:19 +00:00
|
|
|
<title>{{ $name }}{{ $title ?? 'MapleDeploy' }}</title> {{-- MapleDeploy branding --}}
|
|
|
|
|
{{-- MapleDeploy branding: single favicon for all environments --}}
|
|
|
|
|
<link rel="icon" href="{{ asset('mapledeploy-favicon.ico') }}" type="image/x-icon" />
|
2023-08-29 14:31:46 +00:00
|
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
|
|
|
@vite(['resources/js/app.js', 'resources/css/app.css'])
|
2025-10-23 18:46:58 +00:00
|
|
|
<script>
|
|
|
|
|
// Update theme-color meta tag (non-critical, can run async)
|
|
|
|
|
const t = localStorage.theme || 'dark';
|
|
|
|
|
const isDark = t === 'dark' || (t === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
|
|
|
|
|
document.getElementById('theme-color-meta')?.setAttribute('content', isDark ? '#101010' : '#ffffff');
|
|
|
|
|
</script>
|
2023-08-29 14:31:46 +00:00
|
|
|
<style>
|
|
|
|
|
[x-cloak] {
|
|
|
|
|
display: none !important;
|
|
|
|
|
}
|
|
|
|
|
</style>
|
2026-02-16 01:54:19 +00:00
|
|
|
{{-- MapleDeploy branding: Coolify Cloud analytics removed --}}
|
2023-12-06 09:32:49 +00:00
|
|
|
@auth
|
2024-06-25 11:54:44 +00:00
|
|
|
<script type="text/javascript" src="{{ URL::asset('js/echo.js') }}"></script>
|
|
|
|
|
<script type="text/javascript" src="{{ URL::asset('js/pusher.js') }}"></script>
|
|
|
|
|
<script type="text/javascript" src="{{ URL::asset('js/apexcharts.js') }}"></script>
|
2025-08-19 08:34:54 +00:00
|
|
|
<script type="text/javascript" src="{{ URL::asset('js/purify.min.js') }}"></script>
|
2023-12-06 09:32:49 +00:00
|
|
|
@endauth
|
2023-08-29 14:31:46 +00:00
|
|
|
</head>
|
2024-11-26 15:15:41 +00:00
|
|
|
@section('body')
|
|
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
<body class="dark:text-inherit text-black">
|
|
|
|
|
<x-toast />
|
|
|
|
|
<script data-navigate-once>
|
|
|
|
|
// Global HTML sanitization function using DOMPurify
|
|
|
|
|
window.sanitizeHTML = function (html) {
|
|
|
|
|
if (!html) return '';
|
|
|
|
|
const URL_RE = /^(https?:|mailto:)/i;
|
|
|
|
|
const config = {
|
2025-12-04 12:14:44 +00:00
|
|
|
ALLOWED_TAGS: ['a', 'b', 'br', 'code', 'del', 'div', 'em', 'i', 'mark', 'p', 'pre', 's', 'span', 'strong',
|
2025-11-06 07:43:43 +00:00
|
|
|
'u'
|
|
|
|
|
],
|
|
|
|
|
ALLOWED_ATTR: ['class', 'href', 'target', 'title', 'rel'],
|
|
|
|
|
ALLOW_DATA_ATTR: false,
|
|
|
|
|
FORBID_TAGS: ['script', 'object', 'embed', 'applet', 'iframe', 'form', 'input', 'button', 'select',
|
|
|
|
|
'textarea', 'details', 'summary', 'dialog', 'style'
|
|
|
|
|
],
|
|
|
|
|
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onfocus', 'onblur', 'onchange',
|
|
|
|
|
'onsubmit', 'ontoggle', 'style'
|
|
|
|
|
],
|
|
|
|
|
KEEP_CONTENT: true,
|
|
|
|
|
RETURN_DOM: false,
|
|
|
|
|
RETURN_DOM_FRAGMENT: false,
|
|
|
|
|
SANITIZE_DOM: true,
|
|
|
|
|
SANITIZE_NAMED_PROPS: true,
|
|
|
|
|
SAFE_FOR_TEMPLATES: true,
|
|
|
|
|
ALLOWED_URI_REGEXP: URL_RE
|
|
|
|
|
};
|
2025-08-19 08:34:54 +00:00
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
// 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);
|
2025-11-05 07:59:05 +00:00
|
|
|
}
|
2025-11-06 07:43:43 +00:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-10-23 18:46:58 +00:00
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
// 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;
|
2025-11-05 07:59:05 +00:00
|
|
|
}
|
2025-11-06 07:43:43 +00:00
|
|
|
return DOMPurify.sanitize(html, config);
|
|
|
|
|
};
|
2024-06-20 11:17:06 +00:00
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
// Initialize theme if not set
|
|
|
|
|
if (!('theme' in localStorage)) {
|
|
|
|
|
localStorage.theme = 'dark';
|
|
|
|
|
}
|
2025-11-04 20:15:59 +00:00
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
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
|
2024-06-20 11:17:06 +00:00
|
|
|
}
|
2025-11-06 07:43:43 +00:00
|
|
|
}
|
|
|
|
|
@auth
|
2023-12-10 14:57:46 +00:00
|
|
|
window.Pusher = Pusher;
|
|
|
|
|
window.Echo = new Echo({
|
|
|
|
|
broadcaster: 'pusher',
|
2024-11-12 14:53:05 +00:00
|
|
|
cluster: "{{ config('constants.pusher.host') }}" || window.location.hostname,
|
|
|
|
|
key: "{{ config('constants.pusher.app_key') }}" || 'coolify',
|
|
|
|
|
wsHost: "{{ config('constants.pusher.host') }}" || window.location.hostname,
|
2023-12-11 19:01:54 +00:00
|
|
|
wsPort: "{{ getRealtime() }}",
|
|
|
|
|
wssPort: "{{ getRealtime() }}",
|
2023-12-10 14:57:46 +00:00
|
|
|
forceTLS: false,
|
|
|
|
|
encrypted: true,
|
|
|
|
|
enableStats: false,
|
|
|
|
|
enableLogging: true,
|
|
|
|
|
enabledTransports: ['ws', 'wss'],
|
2024-11-25 11:55:21 +00:00
|
|
|
disableStats: true,
|
|
|
|
|
// Add auto reconnection settings
|
|
|
|
|
enabledTransports: ['ws', 'wss'],
|
|
|
|
|
disabledTransports: ['sockjs', 'xhr_streaming', 'xhr_polling'],
|
|
|
|
|
// Attempt to reconnect on connection lost
|
|
|
|
|
autoReconnect: true,
|
|
|
|
|
// Wait 1 second before first reconnect attempt
|
|
|
|
|
reconnectionDelay: 1000,
|
|
|
|
|
// Maximum delay between reconnection attempts
|
|
|
|
|
maxReconnectionDelay: 1000,
|
|
|
|
|
// Multiply delay by this number for each reconnection attempt
|
|
|
|
|
reconnectionDelayGrowth: 1,
|
|
|
|
|
// Maximum number of reconnection attempts
|
|
|
|
|
maxAttempts: 15
|
2023-12-10 14:57:46 +00:00
|
|
|
});
|
2025-11-06 07:43:43 +00:00
|
|
|
@endauth
|
|
|
|
|
let checkHealthInterval = null;
|
|
|
|
|
let checkIfIamDeadInterval = null;
|
2023-08-29 14:31:46 +00:00
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
function changePasswordFieldType(event) {
|
|
|
|
|
let element = event.target
|
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
|
if (element.className === "relative") {
|
|
|
|
|
break;
|
2023-08-31 19:56:53 +00:00
|
|
|
}
|
2025-11-06 07:43:43 +00:00
|
|
|
element = element.parentElement;
|
|
|
|
|
}
|
|
|
|
|
element = element.children[1];
|
|
|
|
|
if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
|
|
|
|
|
if (element.type === 'password') {
|
|
|
|
|
element.type = 'text';
|
|
|
|
|
if (element.disabled) return;
|
|
|
|
|
element.classList.add('truncate');
|
|
|
|
|
this.type = 'text';
|
|
|
|
|
} else {
|
|
|
|
|
element.type = 'password';
|
|
|
|
|
if (element.disabled) return;
|
|
|
|
|
element.classList.remove('truncate');
|
|
|
|
|
this.type = 'password';
|
2023-08-29 14:31:46 +00:00
|
|
|
}
|
2023-08-31 19:56:53 +00:00
|
|
|
}
|
2025-11-06 07:43:43 +00:00
|
|
|
}
|
2023-08-29 14:31:46 +00:00
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
function copyToClipboard(text) {
|
|
|
|
|
navigator?.clipboard?.writeText(text) && window.Livewire.dispatch('success', 'Copied to clipboard.');
|
|
|
|
|
}
|
|
|
|
|
document.addEventListener('livewire:init', () => {
|
|
|
|
|
window.Livewire.on('reloadWindow', (timeout) => {
|
|
|
|
|
if (timeout) {
|
|
|
|
|
setTimeout(() => {
|
2023-08-29 14:31:46 +00:00
|
|
|
window.location.reload();
|
2025-11-06 07:43:43 +00:00
|
|
|
}, timeout);
|
|
|
|
|
return;
|
|
|
|
|
} else {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
window.Livewire.on('info', (message) => {
|
|
|
|
|
if (typeof message === 'string') {
|
|
|
|
|
window.toast('Info', {
|
|
|
|
|
type: 'info',
|
|
|
|
|
description: message,
|
|
|
|
|
})
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (message.length == 1) {
|
|
|
|
|
window.toast('Info', {
|
|
|
|
|
type: 'info',
|
|
|
|
|
description: message[0],
|
|
|
|
|
})
|
|
|
|
|
} else if (message.length == 2) {
|
|
|
|
|
window.toast(message[0], {
|
|
|
|
|
type: 'info',
|
|
|
|
|
description: message[1],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
window.Livewire.on('error', (message) => {
|
|
|
|
|
if (typeof message === 'string') {
|
|
|
|
|
window.toast('Error', {
|
|
|
|
|
type: 'danger',
|
|
|
|
|
description: message,
|
|
|
|
|
})
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (message.length == 1) {
|
|
|
|
|
window.toast('Error', {
|
|
|
|
|
type: 'danger',
|
|
|
|
|
description: message[0],
|
|
|
|
|
})
|
|
|
|
|
} else if (message.length == 2) {
|
|
|
|
|
window.toast(message[0], {
|
|
|
|
|
type: 'danger',
|
|
|
|
|
description: message[1],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
window.Livewire.on('warning', (message) => {
|
|
|
|
|
if (typeof message === 'string') {
|
|
|
|
|
window.toast('Warning', {
|
|
|
|
|
type: 'warning',
|
|
|
|
|
description: message,
|
|
|
|
|
})
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (message.length == 1) {
|
|
|
|
|
window.toast('Warning', {
|
|
|
|
|
type: 'warning',
|
|
|
|
|
description: message[0],
|
|
|
|
|
})
|
|
|
|
|
} else if (message.length == 2) {
|
|
|
|
|
window.toast(message[0], {
|
|
|
|
|
type: 'warning',
|
|
|
|
|
description: message[1],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
window.Livewire.on('success', (message) => {
|
|
|
|
|
if (typeof message === 'string') {
|
|
|
|
|
window.toast('Success', {
|
|
|
|
|
type: 'success',
|
|
|
|
|
description: message,
|
|
|
|
|
})
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (message.length == 1) {
|
|
|
|
|
window.toast('Success', {
|
|
|
|
|
type: 'success',
|
|
|
|
|
description: message[0],
|
|
|
|
|
})
|
|
|
|
|
} else if (message.length == 2) {
|
|
|
|
|
window.toast(message[0], {
|
|
|
|
|
type: 'success',
|
|
|
|
|
description: message[1],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
2024-11-26 15:15:41 +00:00
|
|
|
@show
|
2023-08-29 14:31:46 +00:00
|
|
|
|
2025-11-06 07:43:43 +00:00
|
|
|
</html>
|