Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c848a79
fix(credentials): stop calling every credential a registry credential
grantemsley Oct 9, 2026
f2b5653
test(credentials): name the useCredential test after what it checks
grantemsley Oct 9, 2026
d00c999
fix(git): let the Credentials tab's git Test wait 15 seconds, not 60
grantemsley Oct 9, 2026
1e3586e
fix(git): say what to do when a reused deploy key is another reposito…
grantemsley Oct 9, 2026
11f9e2a
fix(git): say why ssh-keyscan got no host keys
grantemsley Oct 9, 2026
7b97d32
fix(git): deploy in the checks' folder and environment, whatever star…
grantemsley Oct 9, 2026
193d331
feat(git): show a git stack's repository on the editor's Sources tab
grantemsley Oct 6, 2026
2625a09
feat(git): add a git stack from the Add Stack dialog
grantemsley Oct 6, 2026
eb6b6cc
refactor(git): build a git deploy's compose.sh command in one place
grantemsley Oct 7, 2026
bd8345e
feat(git): deploy a git stack from its stack menu
grantemsley Oct 6, 2026
7c2cb46
feat(git): say in the editor where a git stack's compose file lives
grantemsley Oct 6, 2026
7fd680b
feat(git): show a git stack's branch and deployed commit in the stack…
grantemsley Oct 6, 2026
7ede42a
feat(git): check a git stack for a newer commit from its stack menu
grantemsley Oct 6, 2026
0ee8692
feat(git): move an existing stack into git from the Sources tab
grantemsley Oct 6, 2026
c75b2b4
feat(git): say what deleting a git stack leaves behind
grantemsley Oct 6, 2026
2131edb
docs: git stacks in the web UI
grantemsley Oct 6, 2026
a61c33b
test(e2e): add, deploy and delete a git stack through the web UI
grantemsley Oct 6, 2026
ac147fe
feat(git): show changes made in the clone in an orange box, also abov…
grantemsley Oct 10, 2026
e0924f1
fix(git): read the deployed commit under the lock when checking for c…
grantemsley Oct 12, 2026
760f098
fix(git): name the deploy key boxes for screen readers
grantemsley Oct 12, 2026
7a1b4df
fix(git): say a full commit id can also be 64 characters
grantemsley Oct 12, 2026
b040a7e
feat(git): one deploy key for every ssh stack of a repository
grantemsley Oct 12, 2026
f515f49
feat(git): move an ssh stack onto another deploy key of its repository
grantemsley Oct 12, 2026
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
126 changes: 102 additions & 24 deletions docs/git-stacks.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion source/compose.manager/compose.manager.settings.page
Original file line number Diff line number Diff line change
Expand Up @@ -2622,7 +2622,7 @@ $acePath = file_exists('/usr/local/emhttp/plugins/dynamix/javascript/ace/ace.js'
</div>

<div id="compose-tab-credentials" class="compose-tab-content">
<div class="title"><span class="left"><i class="fa fa-key icon"></i>_(Registry Credentials)_</span></div>
<div class="title"><span class="left"><i class="fa fa-key icon"></i>_(Credentials)_</span></div>
<p>Store pull credentials locally and assign them to individual stacks.</p>
<button type="button" id="add-credential-button"><i class="fa fa-plus"></i> Add credential</button>
<table class="tablesorter" style="width:100%;margin-top:16px;">
Expand Down
23 changes: 22 additions & 1 deletion source/compose.manager/include/ComposeList.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_once("/usr/local/emhttp/plugins/compose.manager/include/Defines.php");
require_once("/usr/local/emhttp/plugins/compose.manager/include/Util.php");
require_once("/usr/local/emhttp/plugins/compose.manager/include/ColumnLayout.php");
require_once("/usr/local/emhttp/plugins/compose.manager/include/GitStackWebActions.php");

$cfg = parse_plugin_cfg($sName);

Expand Down Expand Up @@ -346,7 +347,7 @@ function composeRowBuildFailurePayload(string $composeRoot, string $project, str
$hasBuild = $stackInfo->hasBuildConfig() ? '1' : '0';

// Main row - Docker tab structure with expand arrow on left
$o .= "<tr class='compose-sortable' id='stack-row-$id' data-project='$projectHtml' data-projectname='$projectNameHtml' data-path='$pathHtml' data-isup='$isup' data-profiles='$profilesJson' data-running-profile='$runningProfilesHtml' data-default-profile='$defaultProfilesHtml' data-webui='$webuiUrlHtml' data-containers='$containerNamesAttr' data-ctids='$containerIdsAttr' data-hasbuild='$hasBuild' data-invalid-indirect='" . ($hasInvalidIndirect ? '1' : '0') . "' data-invalid-indirect-path='$invalidIndirectPathHtml' data-identity-blocked='" . ($identityBlocked ? '1' : '0') . "' data-identity-message='$identityMessageHtml' data-identity-folder='$identityFolderHtml' data-identity-legacy='$identityLegacyHtml'>";
$o .= "<tr class='compose-sortable' id='stack-row-$id' data-project='$projectHtml' data-projectname='$projectNameHtml' data-path='$pathHtml' data-isup='$isup' data-profiles='$profilesJson' data-running-profile='$runningProfilesHtml' data-default-profile='$defaultProfilesHtml' data-webui='$webuiUrlHtml' data-containers='$containerNamesAttr' data-ctids='$containerIdsAttr' data-hasbuild='$hasBuild' data-invalid-indirect='" . ($hasInvalidIndirect ? '1' : '0') . "' data-invalid-indirect-path='$invalidIndirectPathHtml' data-identity-blocked='" . ($identityBlocked ? '1' : '0') . "' data-identity-message='$identityMessageHtml' data-identity-folder='$identityFolderHtml' data-identity-legacy='$identityLegacyHtml' data-gitstack='" . ($stackInfo->isGitStack() ? '1' : '0') . "'>";

// Arrow column
$o .= "<td class='col-arrow'>";
Expand Down Expand Up @@ -386,6 +387,26 @@ function composeRowBuildFailurePayload(string $composeRoot, string $project, str
$o .= "<div class='compose-text-muted' style='margin-top:4px;font-size:0.85em;'>";
$o .= "Project: $projectHtml";
$o .= "</div>";
// A git stack's branch and deployed commit
if ($stackInfo->isGitStack()) {
$git = GitStackWebActions::listSummary($stackInfo->path);
$o .= "<div class='compose-text-muted compose-git-line' style='font-size:0.85em;text-align:left;'><i class='fa fa-code-fork'></i> ";
if ($git['problem'] !== null) {
$o .= "<span class='orange-text' title='" . htmlspecialchars($git['problem'], ENT_QUOTES, 'UTF-8') . "'>git settings cannot be read</span>";
} else {
$o .= "<span style='white-space:nowrap;'>" . htmlspecialchars((string) $git['branch'], ENT_QUOTES, 'UTF-8') . ' @ ';
$o .= $git['deployedCommit'] === null
? 'not deployed'
: "<span title='Deployed commit " . htmlspecialchars($git['deployedCommit'], ENT_QUOTES, 'UTF-8') . "'>"
. htmlspecialchars(substr($git['deployedCommit'], 0, 7), ENT_QUOTES, 'UTF-8') . '</span>';
$o .= '</span>';
if ($git['failedCommit'] !== null) {
$failedTitle = 'The deploy of commit ' . substr($git['failedCommit'], 0, 12) . ' failed. Fix it and deploy again.';
$o .= " <span class='red-text' title='" . htmlspecialchars($failedTitle, ENT_QUOTES, 'UTF-8') . "'>failed</span>";
}
}
$o .= "</div>";
}
$o .= "</span>";
$o .= "</td>";

Expand Down
36 changes: 35 additions & 1 deletion source/compose.manager/include/ComposeManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,10 @@ function compose_manager_cpu_spec_count($cpuSpec)
<!-- ========== COMPOSE EDITOR PANEL ========== -->
<div class="editor-panel active" id="editor-panel-compose" role="tabpanel" aria-labelledby="editor-tab-compose">
<div class="editor-modal-body">
<div id="editor-compose-git-banner" class="compose-status-warning" style="display:none;padding:6px 12px;font-size:0.9em;">
<i class="fa fa-code-fork"></i> <strong>This is a git stack.</strong> This file is in the plugin's clone of the repository: change it in the repository, then use Pull and Redeploy. An edit saved here is a local change, which the next deploy stops on (it can save it as a patch and discard it). Changes that must survive deploys belong in the Labels tab (the plugin's override) or the .env tab.
</div>
<div id="editor-compose-git-changed" class="compose-git-warning-box" style="display:none;margin:6px 12px;"></div>
<div id="compose-file-selector-wrap" style="display:none;align-items:center;gap:8px;padding:6px 12px;">
<label for="compose-file-selector" style="margin:0;">File:</label>
<select id="compose-file-selector" onchange="switchComposeFile(this.value)"></select>
Expand Down Expand Up @@ -621,7 +625,33 @@ function compose_manager_cpu_spec_count($cpuSpec)
<div class="settings-section">
<div class="settings-section-title"><i class="fa fa-files-o"></i> Compose Sources &amp; Files</div>

<div class="settings-field">
<!-- Shown instead of Compose Source for a git stack -->
<div class="settings-field" id="settings-git-source" style="display:none;">
<label>Git Repository</label>
<div class="settings-field-help" style="margin-bottom:8px;">This stack is deployed from a git repository. Its compose file is the one in the plugin's clone of the repository.</div>
<div id="settings-git-source-loading" class="compose-text-muted">Loading...</div>
<div id="settings-git-source-error" class="compose-status-danger" style="display:none;"></div>
<table id="settings-git-source-table" class="settings-git-source-table" style="display:none;">
<tbody>
<tr><th>Repository</th><td><code id="settings-git-url"></code></td></tr>
<tr><th>Branch</th><td><code id="settings-git-branch"></code></td></tr>
<tr><th>Compose file</th><td><code id="settings-git-compose-path"></code></td></tr>
<tr><th>Clone</th><td><code id="settings-git-clone-dir"></code></td></tr>
<tr id="settings-git-credential-row"><th>Credential</th><td id="settings-git-credential"></td></tr>
<tr><th>Deployed commit</th><td id="settings-git-deployed"></td></tr>
<tr id="settings-git-failed-row"><th>Failed commit</th><td id="settings-git-failed" class="compose-status-danger"></td></tr>
<tr><th>Checked out</th><td id="settings-git-checked-out"></td></tr>
</tbody>
</table>
<div id="settings-git-local-changes" class="compose-git-warning-box" style="display:none;margin-top:8px;"></div>
<div id="settings-git-problem" class="compose-status-danger" style="display:none;margin-top:8px;"></div>
<div id="settings-git-deploy-key-wrap" style="display:none;margin-top:8px;">
<div class="settings-field-help" id="settings-git-deploy-key-label">The deploy key this stack uses, shared by the ssh stacks of its repository. The repository needs it as a read-only deploy key:</div>
<textarea id="settings-git-deploy-key" rows="2" readonly aria-labelledby="settings-git-deploy-key-label"></textarea>
</div>
</div>

<div class="settings-field" id="settings-compose-source-field">
<label>Compose Source</label>
<div class="settings-field-help" style="margin-bottom:8px;">Where does this stack's compose file live?</div>
<div id="settings-compose-source-radios" style="display:flex;flex-direction:column;gap:6px;">
Expand Down Expand Up @@ -656,6 +686,10 @@ function compose_manager_cpu_spec_count($cpuSpec)
<span class="compose-status-warning" style="font-size:0.9em;"><i class="fa fa-info-circle"></i> This stack uses an external compose source. The Compose editor tab loads and saves from that location.</span>
</div>
<div id="settings-external-compose-file-error" class="compose-status-danger" style="margin-top:6px;display:none;font-size:0.9em;"></div>
<div id="settings-convert-to-git" style="margin-top:12px;">
<button type="button" class="btn btn-sm" style="padding:2px 12px;font-size:0.9em;" onclick="openConvertToGitModal()"><i class="fa fa-code-fork"></i> Move this stack into git...</button>
<div class="settings-field-help">Deploy this stack from a git repository from now on. Put its compose file in the repository first.</div>
</div>
</div>

<div class="settings-field">
Expand Down
5 changes: 5 additions & 0 deletions source/compose.manager/include/ComposeUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
'background' => $background
]);
break;
case 'composeGitDeploy':
echoGitDeployCommand([
'background' => $background
]);
break;
case 'composePull':
echoComposeCommand('pull', [
'background' => $background
Expand Down
Loading
Loading