docker-compose-editor #1
|
@ -0,0 +1,7 @@
|
|||
.code-editor {
|
||||
position: relative;
|
||||
border-top: 15px solid #282c34;
|
||||
border-radius: 15px;
|
||||
height: 750px;
|
||||
width: 100%;
|
||||
}
|
|
@ -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 |
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
// Elements
|
||||
|
||||
// Load initial settings
|
||||
document.onload = initPage();
|
||||
|
||||
// Function to initialize the page
|
||||
function initPage() {
|
||||
}
|
|
@ -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:;"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue