Add docker compose editor page

This commit is contained in:
Youssof 2024-10-30 19:43:36 +00:00
parent f4a278c691
commit 9219d5c554
7 changed files with 232 additions and 61 deletions

7
ui/base1/spiri.css Normal file
View File

@ -0,0 +1,7 @@
.code-editor {
position: relative;
border-top: 15px solid #282c34;
border-radius: 15px;
height: 750px;
width: 100%;
}

View File

@ -0,0 +1,15 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="36" height="36" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier">
<path fill="#06c" d="M12.342 4.536l.15-.227.262.159.116.083c.28.216.869.768.996 1.684.223-.04.448-.06.673-.06.534 0 .893.124 1.097.227l.105.057.068.045.191.156-.066.2a2.044 2.044 0 01-.47.73c-.29.299-.8.652-1.609.698l-.178.005h-.148c-.37.977-.867 2.078-1.702 3.066a7.081 7.081 0 01-1.74 1.488 7.941 7.941 0 01-2.549.968c-.644.125-1.298.187-1.953.185-1.45 0-2.73-.288-3.517-.792-.703-.449-1.243-1.182-1.606-2.177a8.25 8.25 0 01-.461-2.83.516.516 0 01.432-.516l.068-.005h10.54l.092-.007.149-.016c.256-.034.646-.11.92-.27-.328-.543-.421-1.178-.268-1.854a3.3 3.3 0 01.3-.81l.108-.187zM2.89 5.784l.04.007a.127.127 0 01.077.082l.006.04v1.315l-.006.041a.127.127 0 01-.078.082l-.039.006H1.478a.124.124 0 01-.117-.088l-.007-.04V5.912l.007-.04a.127.127 0 01.078-.083l.039-.006H2.89zm1.947 0l.039.007a.127.127 0 01.078.082l.006.04v1.315l-.007.041a.127.127 0 01-.078.082l-.039.006H3.424a.125.125 0 01-.117-.088L3.3 7.23V5.913a.13.13 0 01.085-.123l.039-.007h1.413zm1.976 0l.039.007a.127.127 0 01.077.082l.007.04v1.315l-.007.041a.127.127 0 01-.078.082l-.039.006H5.4a.124.124 0 01-.117-.088l-.006-.04V5.912l.006-.04a.127.127 0 01.078-.083l.039-.006h1.413zm1.952 0l.039.007a.127.127 0 01.078.082l.007.04v1.315a.13.13 0 01-.085.123l-.04.006H7.353a.124.124 0 01-.117-.088l-.006-.04V5.912l.006-.04a.127.127 0 01.078-.083l.04-.006h1.412zm1.97 0l.039.007a.127.127 0 01.078.082l.006.04v1.315a.13.13 0 01-.085.123l-.039.006H9.322a.124.124 0 01-.117-.088l-.006-.04V5.912l.006-.04a.127.127 0 01.078-.083l.04-.006h1.411zM4.835 3.892l.04.007a.127.127 0 01.077.081l.007.041v1.315a.13.13 0 01-.085.123l-.039.007H3.424a.125.125 0 01-.117-.09l-.007-.04V4.021a.13.13 0 01.085-.122l.039-.007h1.412zm1.976 0l.04.007a.127.127 0 01.077.081l.007.041v1.315a.13.13 0 01-.085.123l-.039.007H5.4a.125.125 0 01-.117-.09l-.006-.04V4.021l.006-.04a.127.127 0 01.078-.082l.039-.007h1.412zm1.953 0c.054 0 .1.037.117.088l.007.041v1.315a.13.13 0 01-.085.123l-.04.007H7.353a.125.125 0 01-.117-.09l-.006-.04V4.021l.006-.04a.127.127 0 01.078-.082l.04-.007h1.412zm0-1.892c.054 0 .1.037.117.088l.007.04v1.316a.13.13 0 01-.085.123l-.04.006H7.353a.124.124 0 01-.117-.088l-.006-.04V2.128l.006-.04a.127.127 0 01.078-.082L7.353 2h1.412z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -5,32 +5,51 @@
<title>Docker Compose Status</title>
<meta charset="utf-8">
<link href="../base1/bootstrap.min.css" type="text/css" rel="stylesheet">
<link href="../base1/spiri.css" type="text/css" rel="stylesheet">
<script src="../base1/cockpit.js"></script>
<script src="../base1/jquery-3.7.1.min.js"></script>
</head>
<!-- <div id="editor" class="editor-border" style="height: 500px; width: 100%;"></div> -->
<body>
<div class="container mt-5 mb-5">
<div class="row">
<div class="col-12">
<h2 class="fw-bold">Docker Compose</h2>
<p>Docker Compose page.</p>
<h2 class="fw-bold">Docker Compose Editor</h2>
<p>
Docker compose files define the logic and configuration settings for many services present on the drone regardless of their current active state. Changes to these files will only take effect once saved and activated. Simply rebooting the drone will <u>not</u> apply the changes.
<br><br>The current health status of the drone's docker containers can be found in the <a href="">Docker Status</a> page.
</p>
</div>
</div>
<nav>
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="docker-health.html">Docker Health Status</a>
</li>
<li class="nav-item">
<a class="nav-link" href="docker-compose.html">Docker Compose Editing</a>
</li>
</ul>
</nav>
<div class="row mt-2">
<div class="col-md-2">
<img src="assets/icons/docker.svg" class="me-2"/>
<label for="composeFile" class="form-label"><b>Compose File</b></label>
<select id="composeFileSelection" class="form-select mt-2"></select>
</div>
<div class="col-12 mt-2">
<button class="btn btn-primary" id="save">Save</button>
<button class="btn btn-primary" id="saveAndRun">Save & Run</button>
<button class="btn btn-danger" id="stop">Stop</button>
</div>
<div class="col-12 mt-2">
<pre id="result" class="mt-2 mb-3"></pre>
</div>
</div>
<div class="row mt-2">
<div class="col-12">
<div id="composeEditor" class="code-editor"></div>
</div>
</div>
</div>
<script src="../base1/ace/ace.js"></script>
<script src="docker-compose.js"></script>
</body>
</html>

View File

@ -0,0 +1,173 @@
// Elements
const composeEditor = document.getElementById("composeEditor");
const composeFileSelection = document.getElementById("composeFileSelection");
const result = document.getElementById("result");
const saveButton = document.getElementById("save");
const saveAndRunButton = document.getElementById("saveAndRun");
const stopButton = document.getElementById("stop");
// Load initial settings
document.onload = initPage();
// Event Listeners
composeFileSelection.addEventListener("change", dockerComposeFileChanged);
saveButton.addEventListener("click", saveDockerCompose);
saveAndRunButton.addEventListener("click", saveAndRunDockerCompose);
stopButton.addEventListener("click", stopDockerCompose);
// Initialize the page
function initPage() {
const editor = ace.edit(composeEditor)
// Editor Options
editor.setOptions({
highlightSelectedWord: true,
copyWithEmptySelection: true,
navigateWithinSoftTabs: true,
useSoftTabs: true,
printMargin: false,
fontSize: "20px",
theme: "ace/theme/cloud_editor_dark",
});
editor.session.setMode("ace/mode/yaml");
// TODO: Replace /home/spiri/services with some root level path
// Search for all docker-compose.yaml files in the services directory and populate the dropdown with directory names
const command = "/usr/bin/ls -d ~/services/*/docker-compose.yaml | sed 's/\\/docker-compose.yaml//'"
cockpit.spawn(["bash", "-c", command])
.then((data) => {
pathPairs = data.split("\n").map((path) => [path, path.replace("/home/spiri/services/", "")]);
addDropDown(composeFileSelection, pathPairs, pathPairs[0][0]);
dockerComposeFileChanged()
})
.catch((error) => displayFail(error));
}
// Callback for when a new docker-compose file is selected
// Populate the editor with the contents of the selected file or clear the editor if no file is selected
function dockerComposeFileChanged() {
if (composeFileSelection.value === "") {
populateEditor("");
return;
}
const composeLocation = composeFileSelection.value + "/docker-compose.yaml";
cockpit.file(composeLocation)
.read().then((content) => successReadFile(content))
.catch(error => failureReadFile(error));
}
// Populate the editor with passed in data
function populateEditor(data) {
const editor = ace.edit(composeEditor);
editor.setValue(data, -1);
}
// Callback for when the file is successfully read
// Currently, it just populates the editor with the data
function successReadFile(data) {
populateEditor(data);
}
// Callback for when the file read fails
// Currently, it just logs the error (obviously)
function failureReadFile(error) {
console.error(error);
}
// Save the contents of the editor to the selected docker-compose file
function saveDockerCompose() {
if (composeFileSelection.value === "") return;
const composeLocation = composeFileSelection.value + "/docker-compose.yaml";
const editor = ace.edit(composeEditor);
const composeData = editor.getValue();
// Read the file and replace the contents with the new data
cockpit.file(composeLocation)
.replace(composeData)
.then(() => displaySuccess("Docker Compose file saved successfully."))
.catch(error => displayFail(error));
}
function getSelectedText() {
const selectedOption = composeFileSelection.selectedOptions[0].text;
return selectedOption ? selectedOption.text : '';
}
// Save the contents of the editor to the selected docker-compose file and restart the services
function saveAndRunDockerCompose() {
if (composeFileSelection.value === "") return;
saveDockerCompose();
const composeLocation = composeFileSelection.value + "/docker-compose.yaml";
const commandBringDown = `docker compose -f ${composeLocation} down`;
const commandBringUp = `docker compose -f ${composeLocation}r up -d`;
const selection = composeFileSelection.selectedOptions[0].text;
// Bring down the services then bring them back up with the new configuration
displaySuccess(`Bringing down '${selection}' services...`);
cockpit.spawn(["bash", "-c", commandBringDown], { superuser: true, err: "message" })
.then(() => {
displaySuccess(`'${selection}' services brought down successfully.`);
displaySuccess(`Applying changes to '${selection}' services and bringing them back up...`);
cockpit.spawn(["bash", "-c", commandBringUp], { superuser: true, err: "message" })
.then(() => displaySuccess("Done."))
.catch(error => displayFail(error.message));
})
.catch(error => displayFail(error.message));
}
// Stop the services in the selected docker-compose file
function stopDockerCompose() {
if (composeFileSelection.value === "") return;
const composeLocation = composeFileSelection.value + "/docker-compose.yaml";
const commandBringDown = `docker compose -f ${composeLocation} down`;
const selection = composeFileSelection.selectedOptions[0].text;
displaySuccess(`Bringing down '${selection}' services...`);
cockpit.spawn(["bash", "-c", commandBringDown], { superuser: true, err: "message" })
.then(() => displaySuccess(`'${selection}' services brought down successfully.`))
.catch(error => displayFail(error.message));
}
// Add options to a dropdown box
function addDropDown(box, pairs, defaultValue) {
try {
for(let i = 0; i < pairs.length; i++){
if (pairs[i].length == 0 || pairs[i][0] === "" || pairs[i][1] === "") continue;
const option = document.createElement("option");
option.value = pairs[i][0];
option.text = pairs[i][1];
box.add(option);
if (defaultValue === option.value) {
box.value = option.value;
}
}
}
catch(e) {
displayFail(e)
}
}
// Display Success result
function displaySuccess(msg) {
msg = `<span style='color: green;'>${msg}</span>`;
if (result.innerHTML === "")
result.innerHTML = msg;
else
result.innerHTML += "<br>" + msg;
setTimeout(() => result.innerHTML = "", 10000);
}
// Display failure message
function displayFail(error) {
console.error(error);
error = `<span style='color: red;'>Error : ${error}</span>`;
if (result.innerHTML === "")
result.innerHTML = error;
else
result.innerHTML += "<br>" + error;
setTimeout(() => result.innerHTML = "", 10000);
}

View File

@ -1,36 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Docker Health Status</title>
<meta charset="utf-8">
<link href="../base1/bootstrap.min.css" type="text/css" rel="stylesheet">
<script src="../base1/cockpit.js"></script>
<script src="../base1/jquery-3.7.1.min.js"></script>
</head>
<body>
<div class="container mt-5 mb-5">
<div class="row">
<div class="col-12">
<h2 class="fw-bold">Docker Health</h2>
<p>Docker Health page.</p>
<nav>
<ul class="nav nav-pills">
<li class="nav-item">
<a class="nav-link" href="docker-health.html">Docker Health Status</a>
</li>
<li class="nav-item">
<a class="nav-link" href="docker-compose.html">Docker Compose Editing</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<script src="docker-health.js"></script>
</body>
</html>

View File

@ -1,9 +0,0 @@
// Elements
// Load initial settings
document.onload = initPage();
// Function to initialize the page
function initPage() {
}

View File

@ -23,8 +23,8 @@
]
},
"Docker": {
"label": "Docker",
"path": "docker-health.html",
"label": "Docker Compose",
"path": "docker-compose.html",
"order": 3,
"keywords": [
{
@ -42,5 +42,7 @@
}
]
}
}
},
"content-security-policy": "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:;"
}