From 7adb04e3707f5928e30e2186bd3c93e173a4650e Mon Sep 17 00:00:00 2001
From: mabasian <54101509+mabasian@users.noreply.github.com>
Date: Mon, 3 Jun 2024 03:13:32 -0700
Subject: [PATCH] FIX: export all logs in the log page (#1899)
* FIX: export all logs in the log page
* REDO: export all logs for best practice
* fix: eslint error
* ADD: loafing for export all logs
* fix: the loading UI
* UPDATE: export all logs function to be faster and more clean
* ADD: 100000 Logs Limit
---------
Co-authored-by: NeoPlays <80448387+NeoPlays@users.noreply.github.com>
---
launcher/public/output.css | 20 +++++--------
launcher/src/backend/Monitoring.js | 25 ++++++++--------
launcher/src/backend/NodeConnection.js | 30 ++++++++++++++++---
launcher/src/background.js | 21 +++++++++----
.../components/UI/node-page/NodeScreen.vue | 18 +++++++++++
.../node-page/components/logs/LogFooter.vue | 19 +++++++++---
.../UI/node-page/sections/LogsSection.vue | 8 +++--
.../UI/the-control/ControlScreen.vue | 16 ++++++++++
launcher/src/store/ControlService.js | 4 +++
launcher/src/store/theNode.js | 3 +-
10 files changed, 123 insertions(+), 41 deletions(-)
diff --git a/launcher/public/output.css b/launcher/public/output.css
index 49761ee96..48d626671 100755
--- a/launcher/public/output.css
+++ b/launcher/public/output.css
@@ -1519,6 +1519,10 @@ video {
margin-top: 10rem;
}
+.mt-5{
+ margin-top: 1.25rem;
+}
+
.mt-6{
margin-top: 1.5rem;
}
@@ -1527,10 +1531,6 @@ video {
margin-top: 2rem;
}
-.mt-5{
- margin-top: 1.25rem;
-}
-
.box-border{
-webkit-box-sizing: border-box;
box-sizing: border-box;
@@ -2098,6 +2098,10 @@ video {
width: 1.5rem;
}
+.w-60{
+ width: 15rem;
+}
+
.w-64{
width: 16rem;
}
@@ -2252,14 +2256,6 @@ video {
width: 100vw;
}
-.w-40{
- width: 10rem;
-}
-
-.w-60{
- width: 15rem;
-}
-
.min-w-\[100px\]{
min-width: 100px;
}
diff --git a/launcher/src/backend/Monitoring.js b/launcher/src/backend/Monitoring.js
index a120565e1..e7469d2b2 100755
--- a/launcher/src/backend/Monitoring.js
+++ b/launcher/src/backend/Monitoring.js
@@ -436,11 +436,11 @@ export class Monitoring {
var query =
rpc_method.trim().indexOf("{") < 0
? JSON.stringify({
- jsonrpc: "2.0",
- method: rpc_method.trim(),
- params: rpc_params,
- id: 1,
- })
+ jsonrpc: "2.0",
+ method: rpc_method.trim(),
+ params: rpc_params,
+ id: 1,
+ })
: rpc_method;
// Define default response
@@ -1575,9 +1575,10 @@ export class Monitoring {
try {
//Nethermind returns the peers per client type (e.g. Geth, Erigon, Nethermind ...), therefore we need to sum them up
if (clt.service == "NethermindService") {
- details[clientType]["numPeer"] = parseInt(xx
- .filter((s) => s.metric.__name__ == services[clientType][clt.service][index])
- .reduce((total, obj) => total + parseInt(obj.value.pop()), 0)
+ details[clientType]["numPeer"] = parseInt(
+ xx
+ .filter((s) => s.metric.__name__ == services[clientType][clt.service][index])
+ .reduce((total, obj) => total + parseInt(obj.value.pop()), 0)
);
} else {
details[clientType]["numPeer"] = parseInt(
@@ -2622,8 +2623,8 @@ export class Monitoring {
const addr_type = Array.isArray(addr)
? "arr"
: typeof addr === "string" && ["public", "local"].includes(addr)
- ? "str"
- : "invalid";
+ ? "str"
+ : "invalid";
addr = addr_type == "str" ? addr.toLowerCase().trim() : addr;
if (addr_type == "invalid") {
return {
@@ -2711,7 +2712,7 @@ export class Monitoring {
for (let i = 0; i < serviceInfos.length; i++) {
const hashDependencies =
serviceInfos[i].config.dependencies.consensusClients.length ||
- serviceInfos[i].config.dependencies.executionClients.length
+ serviceInfos[i].config.dependencies.executionClients.length
? "yes"
: "no";
easyInfos.push({
@@ -3225,7 +3226,7 @@ rm -rf diskoutput
if (result.data === undefined) {
throw result;
}
- const curlTag = await this.nodeConnection.ensureCurlImage()
+ const curlTag = await this.nodeConnection.ensureCurlImage();
const exitMsg = result.data;
const exitCommand = `docker run --rm --network=stereum curlimages/curl:${curlTag} curl 'http://stereum-${serviceId}:${beaconAPIPort}/eth/v1/beacon/pool/voluntary_exits' -H 'accept: */*' -H 'Content-Type: application/json' -d '${JSON.stringify(
exitMsg
diff --git a/launcher/src/backend/NodeConnection.js b/launcher/src/backend/NodeConnection.js
index 15d47e134..15d12a8ae 100755
--- a/launcher/src/backend/NodeConnection.js
+++ b/launcher/src/backend/NodeConnection.js
@@ -160,7 +160,8 @@ export class NodeConnection {
"sudo -u root apt update &&\
sudo -u root apt install -y software-properties-common &&\
sudo -u root add-apt-repository --yes --update ppa:ansible/ansible &&\
- sudo -u root apt install -y pip ansible tar gzip wget git", false
+ sudo -u root apt install -y pip ansible tar gzip wget git",
+ false
);
} catch (err) {
log.error(err);
@@ -2359,7 +2360,7 @@ export class NodeConnection {
throw new Error(SSHService.extractExecError(result));
}
- return "latest"
+ return "latest";
} catch (error) {
// if pulling the latest image fails, try fetching the latest installed image
try {
@@ -2369,8 +2370,13 @@ export class NodeConnection {
throw new Error(SSHService.extractExecError(fetchedImages));
}
- const images = fetchedImages.stdout.split(/\n/).slice(0, -1).map((json) => { return JSON.parse(json) });
- log.info(`installed images: ${images}`)
+ const images = fetchedImages.stdout
+ .split(/\n/)
+ .slice(0, -1)
+ .map((json) => {
+ return JSON.parse(json);
+ });
+ log.info(`installed images: ${images}`);
if (images.length === 0) return "latest";
// get the latest installed image
@@ -2388,4 +2394,20 @@ export class NodeConnection {
}
}
}
+
+ async getAllServiceLogs(args) {
+ const containerName = `stereum-${args}`;
+ try {
+ const logResult = await this.sshService.exec(`docker logs ${containerName} --tail=100000 2>&1`);
+
+ if (logResult.rc || !logResult.stdout || logResult.stderr) {
+ throw new Error(logResult.stderr || "Error fetching logs");
+ }
+
+ return { containerId: containerName, logs: logResult.stdout.trim().split("\n") };
+ } catch (err) {
+ log.error(`Failed to get logs for container ${containerName}: `, err);
+ return { containerId: "ERROR", logs: err.message };
+ }
+ }
}
diff --git a/launcher/src/background.js b/launcher/src/background.js
index be9ed1d46..e13e72ac7 100755
--- a/launcher/src/background.js
+++ b/launcher/src/background.js
@@ -24,7 +24,7 @@ const oneClickInstall = new OneClickInstall();
const serviceManager = new ServiceManager(nodeConnection);
const validatorAccountManager = new ValidatorAccountManager(nodeConnection, serviceManager);
const authenticationService = new AuthenticationService(nodeConnection);
-const sshService = new SSHService;
+const sshService = new SSHService();
const { globalShortcut } = require("electron");
const log = require("electron-log");
const stereumUpdater = new StereumUpdater(log, createWindow, isDevelopment);
@@ -220,6 +220,10 @@ ipcMain.handle("getServiceLogs", async (event, args) => {
return await monitoring.getServiceLogs(args);
});
+ipcMain.handle("getAllServiceLogs", async (event, args) => {
+ return await nodeConnection.getAllServiceLogs(args);
+});
+
ipcMain.handle("getServiceConfig", async (event, args) => {
return await nodeConnection.readServiceConfiguration(args);
});
@@ -497,15 +501,20 @@ ipcMain.handle("getCurrentEpochSlot", async (event, args) => {
ipcMain.handle("beginAuthSetup", async (event, args) => {
const current_window = event.sender;
- return await authenticationService.beginAuthSetup(args.timeBased, args.increaseTimeLimit, args.enableRateLimit, current_window)
+ return await authenticationService.beginAuthSetup(
+ args.timeBased,
+ args.increaseTimeLimit,
+ args.enableRateLimit,
+ current_window
+ );
});
ipcMain.handle("finishAuthSetup", async () => {
- return await authenticationService.finishAuthSetup()
+ return await authenticationService.finishAuthSetup();
});
ipcMain.handle("authenticatorVerification", async (event, args) => {
- return await authenticationService.authenticatorVerification(args)
+ return await authenticationService.authenticatorVerification(args);
});
ipcMain.handle("removeAuthenticator", async (event, args) => {
@@ -517,7 +526,7 @@ ipcMain.handle("checkForAuthenticator", async (event, args) => {
});
ipcMain.handle("cancelVerification", async (event, args) => {
- return await sshService.cancelVerification(args)
+ return await sshService.cancelVerification(args);
});
ipcMain.handle("changePassword", async (event, args) => {
@@ -676,7 +685,7 @@ ipcMain.handle("stopShell", async () => {
ipcMain.handle("create2FAQRCode", async (event, args) => {
return await authenticationService.create2FAQRCode(args.type, args.name, args.ip, args.secret);
-})
+});
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([{ scheme: "app", privileges: { secure: true, standard: true } }]);
diff --git a/launcher/src/components/UI/node-page/NodeScreen.vue b/launcher/src/components/UI/node-page/NodeScreen.vue
index 61e824a0d..a4c9cd797 100755
--- a/launcher/src/components/UI/node-page/NodeScreen.vue
+++ b/launcher/src/components/UI/node-page/NodeScreen.vue
@@ -34,6 +34,7 @@
:client="nodeStore.clientToLogs"
@close-log="closeLogPage"
@export-log="exportLogs"
+ @export-all-log="updateAndExportAllLogs"
/>
@@ -112,6 +113,7 @@ onMounted(() => {
updateConnectionStats();
updateServiceLogs();
+
polling = setInterval(updateServiceLogs, 10000); // refresh logs
pollingVitals = setInterval(updateServerVitals, 1000); // refresh server vitals
pollingNodeStats = setInterval(updateNodeStats, 1000); // refresh server vitals
@@ -157,6 +159,21 @@ const updateServiceLogs = async () => {
nodeStore.serviceLogs = data;
}
};
+
+const updateAndExportAllLogs = async (client) => {
+ nodeStore.isLogLoading = true;
+
+ nodeStore.allLogsForExp = await ControlService.getAllServiceLogs(client.config?.serviceID);
+
+ const fileName = `${client.name}_all_logs.txt`;
+ const data = [...nodeStore.allLogsForExp.logs].reverse();
+ const lineByLine = data.map((line, index) => `#${data.length - index}: ${line}`).join("\n\n");
+ const blob = new Blob([lineByLine], { type: "text/plain;charset=utf-8" });
+ saveAs(blob, fileName);
+
+ nodeStore.isLogLoading = false;
+};
+
const updateServerVitals = async () => {
try {
if (serviceStore.installedServices && serviceStore.installedServices.length > 0 && headerStore.refresh) {
@@ -169,6 +186,7 @@ const updateServerVitals = async () => {
console.log("couldn't check server vitals");
}
};
+
const openExpertModal = (item) => {
nodeStore.isLineHidden = true;
expertModeClient.value = item;
diff --git a/launcher/src/components/UI/node-page/components/logs/LogFooter.vue b/launcher/src/components/UI/node-page/components/logs/LogFooter.vue
index ff708c809..746441344 100644
--- a/launcher/src/components/UI/node-page/components/logs/LogFooter.vue
+++ b/launcher/src/components/UI/node-page/components/logs/LogFooter.vue
@@ -18,7 +18,8 @@
import { useNodeStore } from "@/store/theNode";
-import { ref } from "vue";
+import { ref, computed } from "vue";
const props = defineProps({
client: {
@@ -82,17 +83,27 @@ const props = defineProps({
},
});
-const emit = defineEmits(["export-log"]);
+const emit = defineEmits(["export-log", "export-all-log"]);
const nodeStore = useNodeStore();
const isAllHovered = ref(false);
const is150Hovered = ref(false);
+const loadingIconsClass = computed(() => {
+ return nodeStore.isLogLoading
+ ? "/img/icon/loading-icons/loading-circle.png"
+ : "/img/icon/service-log-icons/all-log-export-button.png";
+});
+
+const isLoadingSpinning = computed(() => {
+ return nodeStore.isLogLoading ? "animate-spin" : "";
+});
+
const exportAllLogs = () => {
nodeStore.exportLogs = false;
nodeStore.exportAllLogs = true;
- emit("export-log", props.client);
+ emit("export-all-log", props.client);
};
const exportLogs = () => {
diff --git a/launcher/src/components/UI/node-page/sections/LogsSection.vue b/launcher/src/components/UI/node-page/sections/LogsSection.vue
index 2ac9219ba..a2cbd640a 100644
--- a/launcher/src/components/UI/node-page/sections/LogsSection.vue
+++ b/launcher/src/components/UI/node-page/sections/LogsSection.vue
@@ -4,7 +4,7 @@
>
-
+
@@ -24,7 +24,7 @@ const { client } = defineProps({
},
});
-const emit = defineEmits(["close-log", "export-log"]);
+const emit = defineEmits(["close-log", "export-log", "export-all-log"]);
const closeLog = () => {
emit("close-log");
@@ -33,4 +33,8 @@ const closeLog = () => {
const exportLog = (item) => {
emit("export-log", item);
};
+
+const exportAllLog = (item) => {
+ emit("export-all-log", item);
+};
diff --git a/launcher/src/components/UI/the-control/ControlScreen.vue b/launcher/src/components/UI/the-control/ControlScreen.vue
index 7b84a3420..4bb55177d 100644
--- a/launcher/src/components/UI/the-control/ControlScreen.vue
+++ b/launcher/src/components/UI/the-control/ControlScreen.vue
@@ -133,6 +133,7 @@
:client="nodeStore.clientToLogs"
@close-log="closeLogPage"
@export-log="exportLogs"
+ @export-all-log="updateAndExportAllLogs"
/>
@@ -226,6 +227,21 @@ const closeLogPage = () => {
isLogsPageActive.value = false;
controlStore.serviceLogs = null;
};
+
+const updateAndExportAllLogs = async (client) => {
+ nodeStore.isLogLoading = true;
+
+ nodeStore.allLogsForExp = await ControlService.getAllServiceLogs(client.config?.serviceID);
+
+ const fileName = `${client.name}_all_logs.txt`;
+ const data = [...nodeStore.allLogsForExp.logs].reverse();
+ const lineByLine = data.map((line, index) => `#${data.length - index}: ${line}`).join("\n\n");
+ const blob = new Blob([lineByLine], { type: "text/plain;charset=utf-8" });
+ saveAs(blob, fileName);
+
+ nodeStore.isLogLoading = false;
+};
+
const exportLogs = async (client) => {
const currentService = nodeStore.serviceLogs.find(
(service) => service.config?.serviceID === client.config?.serviceID
diff --git a/launcher/src/store/ControlService.js b/launcher/src/store/ControlService.js
index 58db95d17..2fdc88f71 100755
--- a/launcher/src/store/ControlService.js
+++ b/launcher/src/store/ControlService.js
@@ -187,6 +187,10 @@ class ControlService extends EventEmitter {
return await this.promiseIpc.send("getServiceLogs", args);
}
+ async getAllServiceLogs(args) {
+ return await this.promiseIpc.send("getAllServiceLogs", args);
+ }
+
async getServiceConfig(args) {
return await this.promiseIpc.send("getServiceConfig", args);
}
diff --git a/launcher/src/store/theNode.js b/launcher/src/store/theNode.js
index f2ab9fef4..37c043a68 100755
--- a/launcher/src/store/theNode.js
+++ b/launcher/src/store/theNode.js
@@ -13,7 +13,8 @@ export const useNodeStore = defineStore("theNode", {
runNodePowerModal: false,
clientToLogs: null,
logs: [],
- exportAllLogs: false,
+ allLogsForExp: false,
+ isLogLoading: false,
exportLogs: false,
searchLogs: "",
serviceLogs: [],