From 9219d5c554029a792769ff873bfe1344124b65ee Mon Sep 17 00:00:00 2001 From: Youssof Date: Wed, 30 Oct 2024 19:43:36 +0000 Subject: [PATCH] Add docker compose editor page --- ui/base1/spiri.css | 7 ++ ui/general/assets/icons/docker.svg | 15 +++ ui/general/docker-compose.html | 45 +++++--- ui/general/docker-compose.js | 173 +++++++++++++++++++++++++++++ ui/general/docker-health.html | 36 ------ ui/general/docker-health.js | 9 -- ui/general/manifest.json | 8 +- 7 files changed, 232 insertions(+), 61 deletions(-) create mode 100644 ui/base1/spiri.css create mode 100644 ui/general/assets/icons/docker.svg delete mode 100644 ui/general/docker-health.html delete mode 100644 ui/general/docker-health.js diff --git a/ui/base1/spiri.css b/ui/base1/spiri.css new file mode 100644 index 0000000..52b0b69 --- /dev/null +++ b/ui/base1/spiri.css @@ -0,0 +1,7 @@ +.code-editor { + position: relative; + border-top: 15px solid #282c34; + border-radius: 15px; + height: 750px; + width: 100%; +} \ No newline at end of file diff --git a/ui/general/assets/icons/docker.svg b/ui/general/assets/icons/docker.svg new file mode 100644 index 0000000..a0407b9 --- /dev/null +++ b/ui/general/assets/icons/docker.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ui/general/docker-compose.html b/ui/general/docker-compose.html index 5294907..2878568 100644 --- a/ui/general/docker-compose.html +++ b/ui/general/docker-compose.html @@ -5,32 +5,51 @@ Docker Compose Status + - +
-

Docker Compose

-

Docker Compose page.

+

Docker Compose Editor

+

+ 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 not apply the changes. +

The current health status of the drone's docker containers can be found in the Docker Status page. +

+
+
- +
+
+ + + +
+ +
+ + + +
+ +
+

+            
+
+ +
+
+
+ + \ No newline at end of file diff --git a/ui/general/docker-compose.js b/ui/general/docker-compose.js index e69de29..ad89b53 100644 --- a/ui/general/docker-compose.js +++ b/ui/general/docker-compose.js @@ -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 = `${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); +} \ No newline at end of file diff --git a/ui/general/docker-health.html b/ui/general/docker-health.html deleted file mode 100644 index bb611f5..0000000 --- a/ui/general/docker-health.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Docker Health Status - - - - - - - -
-
-
-

Docker Health

-

Docker Health page.

- - -
-
-
- - - - - \ No newline at end of file diff --git a/ui/general/docker-health.js b/ui/general/docker-health.js deleted file mode 100644 index 8f3dacd..0000000 --- a/ui/general/docker-health.js +++ /dev/null @@ -1,9 +0,0 @@ - -// Elements - -// Load initial settings -document.onload = initPage(); - -// Function to initialize the page -function initPage() { -} diff --git a/ui/general/manifest.json b/ui/general/manifest.json index bbb5f26..25f56a4 100644 --- a/ui/general/manifest.json +++ b/ui/general/manifest.json @@ -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:;" }