// 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);
}