Skip to content

Commit

Permalink
Electron app release through CD (#171)
Browse files Browse the repository at this point in the history
* Build base image in CI and some changes in dockerfile

* Re-add image names in docker-compose.yml

* stop building Base image and update package.json version

* [fix] working electron app on linux

* 0.8.0

* [fixed] working electron app on macOS

* Add detail error message and increase log element height

* refactor html-placeholder and electron/main.js
  • Loading branch information
smahmed776 authored Mar 22, 2023
1 parent 2687e7e commit 810bbe5
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: ["main", "develop"]
branches: ["main", "develop", "electrop-app"]
pull_request:
branches: ["main", "develop"]

Expand Down
Binary file added electron/assets/favicon.icns
Binary file not shown.
Binary file added electron/assets/favicon.ico
Binary file not shown.
Binary file added electron/assets/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions electron/error-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
exports.getErrorDetail = (msg) => {
const errTextsObject = {
"errorduringconnect:thiserrormayindicatethatthedockerdaemonisnotrunning": {
message: "Docker Not Running!",
detail:
"It seems like your docker is not running. Please start docker service and restart this app.",
},
isnotrecognizedasaninternalorexternalcommand: {
message: "Docker Not Found!",
detail:
"It seems like docker is not installed on your machine or docker path is missing in PATH environment.",
},
"docker-compose:commandnotfound": {
message: "Docker-compose Not Found!",
detail:
"It looks like docker-compose is not installed on your machine. Please install docker-compose and restart the app.",
},
"docker:commandnotfound": {
message: "Docker Not Found!",
detail:
"It seems like docker is not installed on your machine or docker path is missing in PATH environment.",
},
isnotrecognizedasanameofacmdlet: {
message: "Docker Not Found!",
detail:
"It seems like docker is not installed on your machine or docker path is missing in PATH environment.",
},
unknownshorthandflag: {
message: "Docker Compose is not installed!",
detail:
"Docker Compose is not found on your machine. Installing Docker-desktop or docker-compose may solve this problem.",
},
permissiondeniedwhiletryingtoconnectto: {
message: "Permission denied!",
detail:
"Permission denied while trying to connect to Docker. Give docker permission to current user may solve this issue.",
},
"docker-credential-desktopresolvestoexecutableincurrentdirectory": {
message: "Error getting credentials",
detail:
"This is because a wrong entry in ~/.docker/config.json was created. Namely credsStore instead of credStore. Changing the entry in ~/.docker/config.json may solve the problem.",
},
};
const errTextObjKeys = Object.keys(errTextsObject);
const errorIndex = errTextObjKeys.findIndex((key) =>
msg.split(" ").join("").includes(key)
);
if (errorIndex === -1) {
return {
message: "Docker compose error!",
detail: msg,
};
}
return errTextsObject[errTextObjKeys[errorIndex]];
};
80 changes: 80 additions & 0 deletions electron/html-placeholder/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<link rel="shortcut icon" href="../assets/favicon.ico" type="image/x-icon">
<title>Flojoy</title>
<style>
html,
body {
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}

.main_container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 90vh;
}

.logo_container {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}

.log_container {
height: 150px;
width: 75%;
padding: 5px;
background-color: black;
color: white;
}

.log_output {
overflow: hidden;
overflow-y: scroll;
height: 100%;
}

.log-message-container {
list-style: none;
width: fit-content;
padding: 5px;
margin: 0;
}

.log-message-container > li {
padding-right: 10px;
white-space: break-spaces;
width: 100%;
padding-bottom: 5px;

}
</style>
</head>

<body>
<div class="main_container">
<h1 style="text-align: center;">Flojoy Desktop</h1>
<div class="logo_container">
<img src="../assets/favicon.png" height="125px" width="125px" alt="Flojoy">
<p id="app-status"></p>
</div>
<div class="log_container">
<div class="log_output">
<ul class="log-message-container" id="log-message" style="font-family: monospace; font-size: 18px;"> </ul>
</div>
</div>
</div>
</body>

</html>
Empty file.
163 changes: 129 additions & 34 deletions electron/main.js
Original file line number Diff line number Diff line change
@@ -1,60 +1,155 @@
const { app, BrowserWindow } = require("electron");
const isDev = app.isPackaged;
const path = require("path");
const util = require("util");
const exec = util.promisify(require("child_process").exec);

const runCommand = async (command) => {
try {
const { stdout, stderr } = await exec(command);
console.log("stdout: ", stdout);
console.log("stderr: ", stderr);
} catch (e) {
console.error(e);
/* eslint-disable @typescript-eslint/no-var-requires */
const { app, BrowserWindow, dialog, Menu } = require("electron");
const path = require("upath");
const child_process = require("child_process");
const { getErrorDetail } = require("./error-helper");


const isProd = app.isPackaged;
const envPath = process.env.PATH;

if (!envPath?.split(":").includes("usr/local/bin")) {
process.env.PATH = [...envPath.split(":"), "usr/local/bin"].join(":");
}

const getReleativePath = (pathStr) =>
path.toUnix(path.join(__dirname, pathStr));

const APP_ICON =
process.platform === "win32"
? getReleativePath("../electron/assets/favicon.ico")
: getReleativePath("../electron/assets/favicon.icns");

const executeCommand = (command, mainWindow, cb) => {
const script = child_process.exec(command);
script.stdout.on("data", function (data) {
mainWindow.webContents.send("msg", data.toString());
if (cb) cb(data);
});
script.stderr.on("data", function (data) {
mainWindow.webContents.send("err", data);
if (cb) cb(data);
});
script.addListener("exit", () => {
if (cb) cb("EXITED_COMMAND");
});
};

const getComposeFilePath = () => {
if (!isProd) {
return "docker-compose.yml";
}
const fileName = "docker-compose-prod.yml";
if (process.platform === "win32") {
return `./resources/${fileName}`;
}
return getReleativePath(`../../${fileName}`);
};

const composeFile = getComposeFilePath();

const createMainWindow = () => {
let mainWindow = new BrowserWindow({
Menu.setApplicationMenu(null);
const mainWindow = new BrowserWindow({
width: 1280,
height: 720,
show: false,
icon: APP_ICON,
backgroundColor: "white",
autoHideMenuBar: true,
webPreferences: {
nodeIntegration: false,
devTools: isDev,
preload: getReleativePath("preload.js"),
devTools: !isProd,
},
});
const loadingPageURL = `file://${getReleativePath(
"./html-placeholder/index.html"
)}`;

const startURL = isDev
? "http://localhost:3000"
: `file://${path.join(__dirname, "../build/index.html")}`;
mainWindow
.loadURL(loadingPageURL)
.then(() =>
mainWindow.webContents.send("app_status", "Initializing Flojoy...")
);
mainWindow.once("ready-to-show", () => {
mainWindow.show();
});
let isClosing = false;
let shouldLoad = true;
let lastResponse = "";

const composeFile = isDev ? "docker-compose.yml" : "docker-compose-prod.yml";
const command = `docker-compose -f ${composeFile} up`;
executeCommand(command, mainWindow, (response) => {
const possibleResponseStr = ["WatchingforfilechangeswithStatReloader"];
const textFoundInResponse = possibleResponseStr.find((str) =>
response.split(" ").join("").includes(str)
);
if (textFoundInResponse) {
if (shouldLoad) {
mainWindow.loadURL(`file://${getReleativePath("../build/index.html")}`);
shouldLoad = false;
}
}
if (response === "EXITED_COMMAND") {
if (!isClosing) {
const { message, detail } = getErrorDetail(lastResponse);
dialog.showMessageBoxSync(mainWindow, {
title: " Failed To Run Docker Containers",
message,
detail,
type: "error",
icon: APP_ICON,
});
}
} else {
lastResponse = response;
}
});

mainWindow.loadURL(startURL);
const composeDownAndClose = () => {
mainWindow
.loadURL(loadingPageURL)
.then(() =>
mainWindow.webContents.send("app_status", "Closing Flojoy...")
);
const composeDownCommand = `docker-compose -f ${composeFile} down`;
isClosing = true;
executeCommand(composeDownCommand, mainWindow, (response) => {
if (response === "EXITED_COMMAND") {
mainWindow.destroy();
if (process.platform !== "darwin") {
app.quit();
}
process.exit();
}
});
};

mainWindow.once("ready-to-show", () => mainWindow.show());
mainWindow.on("close", async (e) => {
e.preventDefault();
const options = {
type: "question",
buttons: ["Yes", "No"],
defaultId: 0,
title: "Confirm",
message: "Are you sure you want to quit?",
};
const { response } = await dialog.showMessageBox(mainWindow, options);
if (response === 0) {
composeDownAndClose();
}
});

mainWindow.on("closed", () => {
mainWindow.destroy();
runCommand(`docker compose -f ${composeFile} down`).then(() => {
if (process.platform !== "darwin") {
app.quit();
}
process.exit();
});
composeDownAndClose();
});

mainWindow.webContents.on("new-window", (event, url) => {
event.preventDefault();
mainWindow.webContents.setWindowOpenHandler((details) => {
const { url } = details;
mainWindow.loadURL(url);
});
};

app.whenReady().then(() => {
runCommand(`docker compose -f ${composeFile} up`);

createMainWindow();
app.on("activate", () => {
if (!BrowserWindow.getAllWindows().length) {
Expand Down
28 changes: 28 additions & 0 deletions electron/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { ipcRenderer } = require("electron");

window.addEventListener("DOMContentLoaded", (_) => {
const outputList = document.getElementById("log-message");
const appStatusElem = document.getElementById("app-status");
ipcRenderer.send("ping");
ipcRenderer.on("msg", (_, arg) => {
const output = document.createElement("li");
output.innerText = arg;
outputList.appendChild(output);
output.children
.item(output.children.length - 1)
.scrollIntoView({ behavior: "smooth" });
});

ipcRenderer.on("err", (_, arg) => {
const output = document.createElement("li");
output.innerText = arg;
outputList.appendChild(output);
output.children
.item(output.children.length - 1)
.scrollIntoView({ behavior: "smooth" });
});
ipcRenderer.on("app_status", (_, arg) => {
appStatusElem.innerHTML = arg.toString();
});
});
Loading

0 comments on commit 810bbe5

Please sign in to comment.