Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions source/compose.manager/compose.manager.dashboard.page
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,13 @@ EOT;
// Debug setting from config
$debugEnabled = isset($cfg['DEBUG_TO_LOG']) && $cfg['DEBUG_TO_LOG'] === 'true' ? 'true' : 'false';
$hideComposeContainersJs = $hideDockerComposeContainers ? 'true' : 'false';
// Icon helpers live in their own file because composeManagerMain.js is not loaded on the Dashboard
$iconsJsSrc = '/plugins/compose.manager/javascript/composeIcons.js';
$iconsJsVer = @filemtime('/usr/local/emhttp' . $iconsJsSrc);
if ($iconsJsVer) $iconsJsSrc .= '?v=' . $iconsJsVer;
// CSS and JavaScript
$configScript = <<<EOT
<script src="$iconsJsSrc"></script>
<script>
window.composeDashDebug = $debugEnabled;
window.hideDockerComposeContainers = $hideComposeContainersJs;
Expand Down Expand Up @@ -547,6 +552,12 @@ $script .= <<<'EOT'
if (data.partial > 0) statusText += ', Partial: ' + data.partial;
$('#compose_stacks_status').text(statusText);

// Hide compose-managed containers from the Docker Containers dashboard tile.
// Done before rendering so a render failure can't disable hiding.
if (window.hideDockerComposeContainers && data.composeContainerNames && data.composeContainerNames.length > 0) {
setupComposeContainerHiding(data.composeContainerNames);
}

// Clear container cache since DOM will be rebuilt
stackContainerCache = {};

Expand Down Expand Up @@ -654,11 +665,6 @@ $script .= <<<'EOT'
}
noStacks();

// Hide compose-managed containers from the Docker Containers dashboard tile
if (window.hideDockerComposeContainers && data.composeContainerNames && data.composeContainerNames.length > 0) {
setupComposeContainerHiding(data.composeContainerNames);
}

debugLog('Loaded', data.stacks.length, 'stacks, context menus attached via onclick');
}, 'json').fail(function() {
$('#compose_stacks_status').text('Stacks -- Error loading');
Expand Down Expand Up @@ -740,7 +746,6 @@ $script .= <<<'EOT'
if (!containerNames || containerNames.length === 0) return;

debugLog('Setting up compose container hiding for:', containerNames);
console.log('[ComposeManager] Container names to hide:', containerNames);

// Build a lookup object for fast case-insensitive matching
composeContainerNameSet = {};
Expand Down Expand Up @@ -790,7 +795,6 @@ $script .= <<<'EOT'
var name = extractContainerName($el);
if (name && composeContainerNameSet[name.toLowerCase()]) {
debugLog('Hiding Docker tile container:', name);
console.log('[ComposeManager] Hiding:', name);
$el.addClass('compose-hidden-container');
hiddenCount++;
}
Expand Down
1 change: 1 addition & 0 deletions source/compose.manager/include/ComposeManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ function compose_manager_cpu_spec_count($cpuSpec)
<script src="<?php autov($acePath . '/ace.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/js-yaml/js-yaml.min.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/common.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/composeIcons.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/composeSortable.js'); ?>" type="text/javascript"></script>
<script src="<?php autov('/plugins/compose.manager/javascript/composeStackUtils.js'); ?>" type="text/javascript"></script>
<?php if (file_exists('/usr/local/emhttp/plugins/docker.versions/styles/styles.css')): ?>
Expand Down
8 changes: 5 additions & 3 deletions source/compose.manager/include/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ function compose_icon_ext_to_mime(string $ext): string
}

if (!function_exists('compose_icon_browser_url')) {
/** Return the URL the browser should use: proxy for http(s), passthrough otherwise. */
/**
* Return the URL the browser should use: proxy for http(s), passthrough otherwise.
* Data URIs are never proxied — the base64 payload would blow past URL length limits.
*/
function compose_icon_browser_url(string $src): string
{
$src = trim($src);
Expand All @@ -172,10 +175,9 @@ function compose_icon_browser_url(string $src): string
}

$isRemote = strncasecmp($src, 'http://', 7) === 0 || strncasecmp($src, 'https://', 8) === 0;
$isData = strncasecmp($src, 'data:image/', 11) === 0;
$isCacheableLocal = str_starts_with($src, '/mnt/') || str_starts_with($src, '/boot/config/plugins/compose.manager/');

if ($isRemote || $isData || $isCacheableLocal) {
if ($isRemote || $isCacheableLocal) {
return '/plugins/compose.manager/IconCache.php?src=' . urlencode($src);
}

Expand Down
61 changes: 61 additions & 0 deletions source/compose.manager/javascript/composeIcons.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Shared icon helpers.
* Loaded by both the Compose page and the Dashboard tile — the tile has no
* access to composeManagerMain.js, so these must live in their own file.
*/

function composeIconFallback(img) {
if (!img || img.dataset.composeFallbackApplied === 'true') {
return;
}
img.dataset.composeFallbackApplied = 'true';
img.onerror = null;
img.src = '/plugins/compose.manager/images/question.png';
}

// Validate an icon source: http(s) URL, data URI, or local server path
function isValidIconSrc(src) {
if (!src) return false;
var s = src.trim();
return s.indexOf('http://') === 0 || s.indexOf('https://') === 0 ||
s.indexOf('data:image/') === 0 || s.indexOf('/') === 0;
}

function isCacheEligibleLocalIconPath(src) {
if (!src) return false;
var s = src.trim();
return s.indexOf('/mnt/') === 0 || s.indexOf('/boot/config/plugins/compose.manager/') === 0;
}

/**
* Route cache-eligible icons through the local cache proxy; passthrough otherwise.
* Data URIs are never proxied — the base64 payload would blow past URL length limits.
*/
function composeIconSrc(src, containerName) {
if (!src || !isValidIconSrc(src)) {
return '/plugins/compose.manager/images/question.png';
}
var s = src.trim();
if (s.indexOf('/plugins/compose.manager/IconCache.php?') === 0) {
return s;
}

var cacheable = s.indexOf('http://') === 0 || s.indexOf('https://') === 0 ||
isCacheEligibleLocalIconPath(s);
if (cacheable) {
var proxied = '/plugins/compose.manager/IconCache.php?src=' + encodeURIComponent(s);
if (containerName && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(containerName)) {
Comment thread
mstrhakr marked this conversation as resolved.
proxied += '&ct=' + encodeURIComponent(containerName);
}
return proxied;
}

return s;
}

if (typeof window !== 'undefined') {
window.composeIconFallback = composeIconFallback;
window.isValidIconSrc = isValidIconSrc;
window.isCacheEligibleLocalIconPath = isCacheEligibleLocalIconPath;
window.composeIconSrc = composeIconSrc;
}
47 changes: 2 additions & 45 deletions source/compose.manager/javascript/composeManagerMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -2452,51 +2452,8 @@ function isValidWebUIUrl(url) {
}
}

function composeIconFallback(img) {
if (!img || img.dataset.composeFallbackApplied === 'true') {
return;
}
img.dataset.composeFallbackApplied = 'true';
img.onerror = null;
img.src = '/plugins/compose.manager/images/question.png';
}

// Validate an icon source: http(s) URL, data URI, or local server path
function isValidIconSrc(src) {
if (!src) return false;
var s = src.trim();
return s.indexOf('http://') === 0 || s.indexOf('https://') === 0 ||
s.indexOf('data:image/') === 0 || s.indexOf('/') === 0;
}

function isCacheEligibleLocalIconPath(src) {
if (!src) return false;
var s = src.trim();
return s.indexOf('/mnt/') === 0 || s.indexOf('/boot/config/plugins/compose.manager/') === 0;
}

/** Route cache-eligible icons through the local cache proxy; passthrough otherwise. */
function composeIconSrc(src, containerName) {
if (!src || !isValidIconSrc(src)) {
return '/plugins/compose.manager/images/question.png';
}
var s = src.trim();
if (s.indexOf('/plugins/compose.manager/IconCache.php?') === 0) {
return s;
}

var cacheable = s.indexOf('http://') === 0 || s.indexOf('https://') === 0 ||
s.indexOf('data:image/') === 0 || isCacheEligibleLocalIconPath(s);
if (cacheable) {
var proxied = '/plugins/compose.manager/IconCache.php?src=' + encodeURIComponent(s);
if (containerName && /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(containerName)) {
proxied += '&ct=' + encodeURIComponent(containerName);
}
return proxied;
}

return s;
}
// composeIconFallback/isValidIconSrc/isCacheEligibleLocalIconPath/composeIconSrc
// live in composeIcons.js (shared with the dashboard tile).

// Sanitize user-entered icon values before assigning to image src in live preview.
function sanitizeIconPreviewSrc(raw) {
Expand Down
Loading