// 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} 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 = `${msg}`; if (result.innerHTML === "") result.innerHTML = msg; else result.innerHTML += "
" + msg; setTimeout(() => result.innerHTML = "", 10000); } // Display failure message function displayFail(error) { console.error(error); error = `Error : ${error}`; if (result.innerHTML === "") result.innerHTML = error; else result.innerHTML += "
" + error; setTimeout(() => result.innerHTML = "", 10000); }