Skip to content

Commit c29fa61

Browse files
authored
Merge pull request #53 from mstrhakr/feat/standard-classes
Refactor to standard classes for stack/container data
2 parents 1c5097b + 0ca9cc2 commit c29fa61

14 files changed

Lines changed: 2401 additions & 466 deletions

compose.manager.plg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<!ENTITY packageName "&name;-package-&packageVER;">
1010
<!ENTITY packagefile "&packageName;.txz">
1111
<!ENTITY github "mstrhakr/compose_plugin">
12-
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/main/&name;.plg">
12+
<!ENTITY pluginURL "https://raw.githubusercontent.com/&github;/dev/&name;.plg">
1313
<!ENTITY packageURL "https://github.com/&github;/releases/download/v&version;/&packagefile;">
1414
<!ENTITY pluginLOC "/boot/config/plugins/&name;">
1515
<!ENTITY emhttpLOC "/usr/local/emhttp/plugins/&name;">

source/compose.manager/compose.manager.dashboard.page

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -457,24 +457,38 @@ $script = <<<'EOT'
457457
if (response.containers && response.containers.length > 0) {
458458
var html = '';
459459
response.containers.forEach(function(ct) {
460-
// API returns capitalized property names: State, ID, Icon, Name, WebUI, Image, UpdateStatus, LocalSha, RemoteSha
461-
var isRunning = ct.State === 'running';
460+
// Tolerate PascalCase/camelCase field variants from different API paths.
461+
var ctName = ct.Name || ct.name || ct.Service || ct.service || 'unknown';
462+
var ctState = ct.State || ct.state || '';
463+
var ctIdRaw = ct.ID || ct.Id || ct.id || ctName;
464+
var ctId = String(ctIdRaw || ctName);
465+
var ctIdShort = ctId.substring(0, 12);
466+
var ctIcon = ct.Icon || ct.icon || '/plugins/dynamix.docker.manager/images/question.png';
467+
var ctShell = ct.Shell || ct.shell || '/bin/bash';
468+
var ctWebUI = ct.WebUI || ct.webUI || ct.webui || '';
469+
var ctImage = ct.Image || ct.image || '';
470+
var ctUpdateStatus = ct.UpdateStatus || ct.updateStatus || 'unknown';
471+
var ctLocalSha = ct.LocalSha || ct.localSha || '';
472+
var ctRemoteSha = ct.RemoteSha || ct.remoteSha || '';
473+
var ctStartedAt = ct.StartedAt || ct.startedAt || '';
474+
475+
var isRunning = ctState === 'running';
462476
var stateIcon = isRunning ? 'fa-play' : 'fa-square';
463477
var stateColor = isRunning ? 'green-text' : 'red-text';
464478
var stateText = isRunning ? 'started' : 'stopped';
465-
var ctElId = 'dash-ct-' + ct.ID.substring(0, 12);
466-
var imgSrc = ct.Icon || '/plugins/dynamix.docker.manager/images/question.png';
467-
var shell = ct.Shell || '/bin/bash';
468-
var shortId = ct.ID.substring(0, 12);
469-
var webui = resolveContainerWebUI(ct.WebUI);
479+
var ctElId = 'dash-ct-' + ctIdShort;
480+
var imgSrc = ctIcon;
481+
var shell = ctShell;
482+
var shortId = ctIdShort;
483+
var webui = resolveContainerWebUI(ctWebUI);
470484
// Parse image to get repo and tag
471-
var image = ct.Image || '';
485+
var image = ctImage;
472486
var imageDisplay = image.replace(/^.*\//, ''); // Remove registry prefix
473487

474488
// Update status
475-
var updateStatus = ct.UpdateStatus || 'unknown';
476-
var localSha = ct.LocalSha || '';
477-
var remoteSha = ct.RemoteSha || '';
489+
var updateStatus = ctUpdateStatus;
490+
var localSha = ctLocalSha;
491+
var remoteSha = ctRemoteSha;
478492
var updateHtml = '';
479493
if (updateStatus === 'up-to-date') {
480494
updateHtml = '<span class="green-text"><i class="fa fa-check"></i> current</span>';
@@ -485,14 +499,14 @@ $script = <<<'EOT'
485499
}
486500

487501
// Container uptime - use shared smart formatting
488-
var ctUptime = formatUptime(ct.StartedAt, isRunning);
502+
var ctUptime = formatUptime(ctStartedAt, isRunning);
489503

490504
html += '<div class="compose-dash-container">';
491-
html += '<span class="compose-dash-ct-icon" id="' + ctElId + '" data-ct-name="' + ct.Name + '" data-ct-id="' + shortId + '" data-ct-running="' + (isRunning ? '1' : '0') + '" data-ct-webui="' + escapeAttr(webui) + '" data-ct-shell="' + escapeAttr(shell) + '">';
505+
html += '<span class="compose-dash-ct-icon" id="' + ctElId + '" data-ct-name="' + escapeAttr(ctName) + '" data-ct-id="' + escapeAttr(shortId) + '" data-ct-running="' + (isRunning ? '1' : '0') + '" data-ct-webui="' + escapeAttr(webui) + '" data-ct-shell="' + escapeAttr(shell) + '">';
492506
html += '<img src="' + escapeAttr(imgSrc) + '" onerror="this.src=\'/plugins/dynamix.docker.manager/images/question.png\';">';
493507
html += '</span>';
494508
html += '<span class="compose-dash-ct-info">';
495-
html += '<div class="compose-dash-ct-name">' + ct.Name + '</div>';
509+
html += '<div class="compose-dash-ct-name">' + escapeHtml(ctName) + '</div>';
496510
html += '<div class="compose-dash-ct-state ' + stateColor + '"><i class="fa ' + stateIcon + '"></i> ' + stateText + '</div>';
497511
html += '</span>';
498512
html += '<span class="compose-dash-ct-cols">';

source/compose.manager/php/compose_list.php

Lines changed: 23 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -54,47 +54,20 @@
5454

5555
$stackCount++;
5656

57-
$projectName = $project;
58-
if (is_file("$compose_root/$project/name")) {
59-
$projectName = trim(file_get_contents("$compose_root/$project/name"));
60-
}
57+
// Resolve stack identity and metadata via StackInfo
58+
$stackInfo = StackInfo::fromProject($compose_root, $project);
59+
60+
$projectName = $stackInfo->getName();
6161
$id = str_replace(".", "-", $project);
6262
$id = str_replace(" ", "", $id);
6363

64-
// Get the compose file path
65-
$basePath = is_file("$compose_root/$project/indirect")
66-
? trim(file_get_contents("$compose_root/$project/indirect"))
67-
: "$compose_root/$project";
68-
$composeFile = findComposeFile($basePath) ?: "$basePath/" . COMPOSE_FILE_NAMES[0];
69-
// Resolve override via centralized helper (prefer correctly-named indirect override)
70-
$overridePath = OverrideInfo::fromStack($compose_root, $project)->getOverridePath();
71-
72-
// Use docker compose config --services to get accurate service count
73-
// This properly parses YAML, handles overrides, extends, etc.
74-
$definedServices = 0;
75-
if (is_file($composeFile)) {
76-
$files = "-f " . escapeshellarg($composeFile);
77-
if (is_file($overridePath)) {
78-
$files .= " -f " . escapeshellarg($overridePath);
79-
}
64+
// Get the compose file path and override via StackInfo
65+
$composeFile = $stackInfo->composeFilePath ?? ($stackInfo->composeSource . '/' . COMPOSE_FILE_NAMES[0]);
66+
$overridePath = $stackInfo->getOverridePath();
8067

81-
// Get env file if specified
82-
$envFile = "";
83-
if (is_file("$compose_root/$project/envpath")) {
84-
$envPath = trim(file_get_contents("$compose_root/$project/envpath"));
85-
if (is_file($envPath)) {
86-
$envFile = "--env-file " . escapeshellarg($envPath);
87-
}
88-
}
89-
90-
// Use docker compose config --services to list all service names
91-
$cmd = "docker compose $files $envFile config --services 2>/dev/null";
92-
$output = shell_exec($cmd);
93-
if ($output) {
94-
$services = array_filter(explode("\n", trim($output)));
95-
$definedServices = count($services);
96-
}
97-
}
68+
// Use StackInfo's getDefinedServices for accurate service count
69+
$definedServicesList = $stackInfo->getDefinedServices();
70+
$definedServices = count($definedServicesList);
9871

9972
// Get running container info from $containersByProject
10073
// Use directory basename (sanitized) as project key — this matches the -p flag in echoComposeCommand
@@ -137,48 +110,23 @@
137110
$isrestarting = $restartingCount > 0;
138111
$isup = $actualContainerCount > 0;
139112

140-
if (is_file("$compose_root/$project/description")) {
141-
$description = @file_get_contents("$compose_root/$project/description");
142-
$description = str_replace("\r", "", $description);
143-
// Escape HTML first to prevent XSS, then convert newlines to <br>
144-
$description = htmlspecialchars($description, ENT_QUOTES, 'UTF-8');
113+
// Read metadata via StackInfo lazy getters
114+
$descriptionRaw = $stackInfo->getDescription();
115+
if ($descriptionRaw) {
116+
$descriptionRaw = str_replace("\r", "", $descriptionRaw);
117+
$description = htmlspecialchars($descriptionRaw, ENT_QUOTES, 'UTF-8');
145118
$description = str_replace("\n", "<br>", $description);
146119
} else {
147120
$description = "";
148121
}
149122

150-
$autostart = '';
151-
if (is_file("$compose_root/$project/autostart")) {
152-
$autostarttext = @file_get_contents("$compose_root/$project/autostart");
153-
if (strpos($autostarttext, 'true') !== false) {
154-
$autostart = 'checked';
155-
}
156-
}
157-
158-
// Check for custom project icon (URL-based only via icon_url file)
159-
$projectIcon = '';
160-
if (is_file("$compose_root/$project/icon_url")) {
161-
$iconUrl = trim(@file_get_contents("$compose_root/$project/icon_url"));
162-
if (filter_var($iconUrl, FILTER_VALIDATE_URL) && (strpos($iconUrl, 'http://') === 0 || strpos($iconUrl, 'https://') === 0)) {
163-
$projectIcon = $iconUrl;
164-
}
165-
}
123+
$autostart = $stackInfo->getAutostart() ? 'checked' : '';
166124

167-
// Check for stack-level WebUI URL
168-
$webuiUrl = '';
169-
if (is_file("$compose_root/$project/webui_url")) {
170-
$webuiUrlTmp = trim(@file_get_contents("$compose_root/$project/webui_url"));
171-
if (filter_var($webuiUrlTmp, FILTER_VALIDATE_URL) && (strpos($webuiUrlTmp, 'http://') === 0 || strpos($webuiUrlTmp, 'https://') === 0)) {
172-
$webuiUrl = $webuiUrlTmp;
173-
}
174-
}
125+
$projectIcon = $stackInfo->getIconUrl();
126+
$webuiUrl = $stackInfo->getWebUIUrl();
175127

176-
$profiles = array();
177-
if (is_file("$compose_root/$project/profiles")) {
178-
$profilestext = @file_get_contents("$compose_root/$project/profiles");
179-
$profiles = json_decode($profilestext, false);
180-
}
181-
$profilesJson = htmlspecialchars(json_encode($profiles ? $profiles : []), ENT_QUOTES, 'UTF-8');
128+
$profiles = $stackInfo->getProfiles();
129+
$profilesJson = htmlspecialchars(json_encode($profiles ?: []), ENT_QUOTES, 'UTF-8');
182130

183131
// Determine status text and class for badge
184132
$statusText = "Stopped";
@@ -210,7 +158,7 @@
210158
$projectHtml = htmlspecialchars($project, ENT_QUOTES, 'UTF-8');
211159
$descriptionHtml = $description; // Already contains <br> tags from earlier processing
212160
$pathHtml = htmlspecialchars("$compose_root/$project", ENT_QUOTES, 'UTF-8');
213-
$projectIconUrl = htmlspecialchars($projectIcon, ENT_QUOTES, 'UTF-8');
161+
$projectIconUrl = htmlspecialchars($projectIcon ?? '', ENT_QUOTES, 'UTF-8');
214162

215163
// Status like Docker tab (started/stopped with icon)
216164
$status = $isrunning ? ($runningCount == $containerCount ? 'started' : 'partial') : 'stopped';
@@ -231,11 +179,8 @@
231179
$statusLabel = "partial ($runningCount/$containerCount)";
232180
}
233181

234-
// Get stack started_at timestamp from file for uptime calculation
235-
$stackStartedAt = '';
236-
if (is_file("$compose_root/$project/started_at")) {
237-
$stackStartedAt = trim(file_get_contents("$compose_root/$project/started_at"));
238-
}
182+
// Get stack started_at timestamp via StackInfo
183+
$stackStartedAt = $stackInfo->getStartedAt();
239184

240185
// Calculate uptime display from started_at timestamp
241186
$stackUptime = '';

0 commit comments

Comments
 (0)