From 3c9f074a1b04743508aab36a27ea640b2fab25df Mon Sep 17 00:00:00 2001 From: NeoPlays <80448387+NeoPlays@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:03:02 +0200 Subject: [PATCH 1/6] ADD: Fetching Validators for DVT Solutions --- .../src/backend/ValidatorAccountManager.js | 84 +++++++++++++++---- .../ethereum-services/CharonService.js | 4 + launcher/src/composables/validators.js | 7 +- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/launcher/src/backend/ValidatorAccountManager.js b/launcher/src/backend/ValidatorAccountManager.js index 9d5dbbf0f..e3f03827a 100755 --- a/launcher/src/backend/ValidatorAccountManager.js +++ b/launcher/src/backend/ValidatorAccountManager.js @@ -221,23 +221,38 @@ export class ValidatorAccountManager { this.nodeConnection.taskManager.otherTasksHandler(ref, `Listing Keys`); try { let client = await this.nodeConnection.readServiceConfiguration(serviceID); - const result = await this.keymanagerAPI(client, "GET", "/eth/v1/keystores"); + let data = {}; + if (client.service === "CharonService" || client.service === "SSVNetworkService") { + const keys = await this.getDVTKeys(serviceID); + data.data = keys.map((dv) => { + return { + validating_pubkey: client.service === "CharonService" ? dv.distributed_public_key : "0x" + dv.public_key, + derivation_path: "", + readonly: false, + dvt: true, + }; + }); + //Push successful task + this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, data.data); + } else { + const result = await this.keymanagerAPI(client, "GET", "/eth/v1/keystores"); - //Error handling - if (SSHService.checkExecError(result) && result.stderr) throw SSHService.extractExecError(result); - if (!result.stdout) - throw `ReturnCode: ${result.rc}\nStderr: ${result.stderr}\nStdout: ${result.stdout}\nIs Your Consensus Client Running?`; + //Error handling + if (SSHService.checkExecError(result) && result.stderr) throw SSHService.extractExecError(result); + if (!result.stdout) + throw `ReturnCode: ${result.rc}\nStderr: ${result.stderr}\nStdout: ${result.stdout}\nIs Your Consensus Client Running?`; - const data = JSON.parse(result.stdout); - if (data.data === undefined) { - if (data.code === undefined || data.message === undefined) { - throw "Undexpected Error: " + result; + data = JSON.parse(result.stdout); + if (data.data === undefined) { + if (data.code === undefined || data.message === undefined) { + throw "Undexpected Error: " + result; + } + throw data.code + " " + data.message; } - throw data.code + " " + data.message; - } - //Push successful task - this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, result.stdout); + //Push successful task + this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, result.stdout); + } if (!data.data) data.data = []; this.writeKeys(data.data.map((key) => key.validating_pubkey)); @@ -1068,7 +1083,7 @@ export class ValidatorAccountManager { let charonClient = services.find((service) => service.service === "CharonService"); if (!charonClient) throw "Couldn't find CharonService"; const dataDir = path.posix.join(charonClient.getDataDir(), ".charon"); - this.nodeConnection.sshService.exec(`rm -rf ${dataDir}`); + await this.nodeConnection.sshService.exec(`rm -rf ${dataDir}`); const result = await this.nodeConnection.sshService.uploadDirectorySSH(path.normalize(localPath), dataDir); if (result) { log.info("Obol Backup uploaded from: ", localPath); @@ -1077,4 +1092,45 @@ export class ValidatorAccountManager { log.error("Error uploading Obol Backup: ", err); } } + + async getDVTKeys(serviceID) { + const service = (await this.serviceManager.readServiceConfigurations()).find((s) => s.id === serviceID); + if (!service) throw new Error(`Service with id ${serviceID} not found`); + switch (service.service) { + case "CharonService": { + const result = await this.nodeConnection.sshService.exec(service.getReadClusterLockCommand()); + const clusterLock = JSON.parse(result.stdout); + return clusterLock.distributed_validators; + } + case "SSVNetworkService": { + const ssvConfig = await this.nodeConnection.getSSVTotalConfig(serviceID); + //Get Operator ID + const response = await axios.get( + `https://api.ssv.network/api/v4/${service.network}/operators/public_key/` + ssvConfig.privateKeyFileData.publicKey + ); + if (response.status !== 200 && !response?.data?.data?.id) + throw new Error(`Couldn't get Operator ID from SSV Network ${response.status} ${response.statusText}`); + const operatorID = response.data.data.id; + + //get pagination info + let result = await axios.get( + `https://api.ssv.network/api/v4/${service.network}/validators/in_operator/${operatorID}?page=${1}&perPage=100` + ); + if (result.status !== 200) throw new Error(`Couldn't get Validator Keys from SSV Network ${result.status} ${result.statusText}`); + + //get all pages and concat them + for (let i = 1; i <= result.data.pagination.pages; i++) { + const page = await axios.get( + `https://api.ssv.network/api/v4/${service.network}/validators/in_operator/${operatorID}?page=${i}&perPage=100` + ); + if (page.status !== 200) throw new Error(`Couldn't get Validator Keys from SSV Network ${page.status} ${page.statusText}`); + result.data.validators = result.data.validators.concat(page.data.validators); + } + + return result.data.validators; + } + default: + throw new Error(`Service ${service.service} not supported`); + } + } } diff --git a/launcher/src/backend/ethereum-services/CharonService.js b/launcher/src/backend/ethereum-services/CharonService.js index 5d0af7cab..940d8df2c 100755 --- a/launcher/src/backend/ethereum-services/CharonService.js +++ b/launcher/src/backend/ethereum-services/CharonService.js @@ -114,6 +114,10 @@ export class CharonService extends NodeService { return `ls -1 -a ${this.getDataDir()}/.charon`; } + getReadClusterLockCommand() { + return `cat ${this.getDataDir()}/.charon/cluster-lock.json`; + } + //definitionFile as URL or Path to file (default ".charon/cluster-definition.json" by dkg command) getDKGCommand(definitionFile) { return `docker run -u 0 --name "dkg-container" -d -v "${this.getDataDir()}:/opt/charon" ${this.image + ":" + this.imageVersion} dkg ${ diff --git a/launcher/src/composables/validators.js b/launcher/src/composables/validators.js index c72ade3ed..4c135a72c 100644 --- a/launcher/src/composables/validators.js +++ b/launcher/src/composables/validators.js @@ -10,17 +10,14 @@ export async function useListKeys(forceRefresh) { const stakingStore = useStakingStore(); let keyStats = []; - let clients = serviceStore.installedServices.filter( - (s) => s.category == "validator" && s.service != "CharonService" && s.service != "SSVNetworkService" - ); + let clients = serviceStore.installedServices.filter((s) => s.category == "validator"); if ((clients && clients.length > 0 && nodeManageStore.currentNetwork?.network != "") || forceRefresh) { for (let client of clients) { //if there is already a list of keys () if ((client.config.keys === undefined || client.config.keys.length === 0 || forceRefresh) && client.state === "running") { //refresh validaotr list let result = await ControlService.listValidators(client.config.serviceID); - - if (!client.service.includes("Web3Signer")) { + if (!/Web3Signer|CharonService|SSVNetwork/.test(client.service)) { let resultRemote = await ControlService.listRemoteKeys(client.config.serviceID); let remoteKeys = resultRemote.data ? resultRemote.data.map((e) => { From 914192f63c1f58f4ca4d3abf3c9c9f66e12dab3c Mon Sep 17 00:00:00 2001 From: Max Behzadi <69126271+MaxTheGeeek@users.noreply.github.com> Date: Tue, 29 Oct 2024 16:00:53 +0100 Subject: [PATCH 2/6] ADD: ssv & charon to sidebar --- launcher/public/output.css | 336 +++++++++--------- .../staking-page/sections/SidebarSection.vue | 18 +- 2 files changed, 175 insertions(+), 179 deletions(-) diff --git a/launcher/public/output.css b/launcher/public/output.css index 88d7fde67..3342868d4 100755 --- a/launcher/public/output.css +++ b/launcher/public/output.css @@ -1,169 +1,7 @@ @import url("https://fonts.googleapis.com/css2?family=Noto+Sans:wght@100;200;300;400;500;600;700;800;900&display=swap"); -*, ::before, ::after{ - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} - -::-ms-backdrop{ - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} - -::backdrop{ - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-gradient-from-position: ; - --tw-gradient-via-position: ; - --tw-gradient-to-position: ; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; - --tw-contain-size: ; - --tw-contain-layout: ; - --tw-contain-paint: ; - --tw-contain-style: ; -} - /* -! tailwindcss v3.4.13 | MIT License | https://tailwindcss.com +! tailwindcss v3.4.6 | MIT License | https://tailwindcss.com */ /* @@ -640,6 +478,168 @@ video { scrollbar-width: initial; } +*, ::before, ::after{ + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::-ms-backdrop{ + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + +::backdrop{ + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; + --tw-contain-size: ; + --tw-contain-layout: ; + --tw-contain-paint: ; + --tw-contain-style: ; +} + ::-webkit-scrollbar { background-color: transparent; width: 5px; @@ -2410,11 +2410,6 @@ video { flex: none; } -.flex-shrink{ - -ms-flex-negative: 1; - flex-shrink: 1; -} - .flex-shrink-0{ -ms-flex-negative: 0; flex-shrink: 0; @@ -2736,11 +2731,6 @@ video { flex-direction: column; } -.flex-wrap{ - -ms-flex-wrap: wrap; - flex-wrap: wrap; -} - .place-content-center{ place-content: center; } diff --git a/launcher/src/components/UI/staking-page/sections/SidebarSection.vue b/launcher/src/components/UI/staking-page/sections/SidebarSection.vue index 240ad406c..011d32fb0 100644 --- a/launcher/src/components/UI/staking-page/sections/SidebarSection.vue +++ b/launcher/src/components/UI/staking-page/sections/SidebarSection.vue @@ -4,7 +4,9 @@ @pointerdown.prevent.stop @mousedown.prevent.stop > -
+
{ let services = []; if (setupStore.selectedSetup === null) { - services = serviceStore.installedServices.filter((s) => s.category === "validator" && !/SSVNetwork|Charon/.test(s.service)); + services = serviceStore.installedServices.filter((s) => s.category === "validator"); } else { services = serviceStore.installedServices .filter( (s) => s.category === "validator" && - !/SSVNetwork|Charon/.test(s.service) && - setupStore.selectedSetup?.services?.map((s) => s.config.serviceID).includes(s.config.serviceID) + setupStore.selectedSetup?.services + ?.map((s) => s.config.serviceID) + .includes(s.config.serviceID) ) .map((service) => ({ ...service, selected: false })); } @@ -87,7 +91,9 @@ onMounted(() => { const getCurrentService = () => { if (setupStore.selectedSetup) { const matchingService = installedValidators.value.find((validator) => - setupStore.selectedSetup.services.some((service) => service.id === validator.config?.serviceID) + setupStore.selectedSetup.services.some( + (service) => service.id === validator.config?.serviceID + ) ); currentService.value = matchingService?.config?.serviceID; } else { From e6bdb19fbb0f1cffa7020b3ab9128249a9c65713 Mon Sep 17 00:00:00 2001 From: NeoPlays <80448387+NeoPlays@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:20:13 +0100 Subject: [PATCH 3/6] ADD: dvt property to frontend state --- launcher/src/backend/ValidatorAccountManager.js | 2 +- launcher/src/composables/validators.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/launcher/src/backend/ValidatorAccountManager.js b/launcher/src/backend/ValidatorAccountManager.js index e3f03827a..4be6dc8ec 100755 --- a/launcher/src/backend/ValidatorAccountManager.js +++ b/launcher/src/backend/ValidatorAccountManager.js @@ -233,7 +233,7 @@ export class ValidatorAccountManager { }; }); //Push successful task - this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, data.data); + this.nodeConnection.taskManager.otherTasksHandler(ref, `Get Keys`, true, JSON.stringify(data.data, null, 2)); } else { const result = await this.keymanagerAPI(client, "GET", "/eth/v1/keystores"); diff --git a/launcher/src/composables/validators.js b/launcher/src/composables/validators.js index 4c135a72c..de744ea53 100644 --- a/launcher/src/composables/validators.js +++ b/launcher/src/composables/validators.js @@ -36,7 +36,7 @@ export async function useListKeys(forceRefresh) { //update service config (pinia) client.config.keys = result.data ? result.data.map((e) => { - return { key: e.validating_pubkey, isRemote: e.readonly }; + return { key: e.validating_pubkey, isRemote: e.readonly, dvt: e.dvt ? e.dvt : false }; }) : []; @@ -61,6 +61,7 @@ export async function useListKeys(forceRefresh) { balance: "-", network: client.config.network, isRemote: key.isRemote, + dvt: key.dvt ? key.dvt : false, }; }) ); From 1645daeae651c1feca723919ee7481dd43ef1e81 Mon Sep 17 00:00:00 2001 From: Max Behzadi <69126271+MaxTheGeeek@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:30:52 +0100 Subject: [PATCH 4/6] ADD: first structure for obol, ssv and csm --- launcher/public/output.css | 37 +++++ .../UI/staking-page/StakingScreen.vue | 152 +++++++++++++----- .../staking-page/components/list/ListBody.vue | 23 +-- .../components/list/rows/KeyRow.vue | 70 ++++++-- .../management/ValidatorRewards.vue | 80 ++++++++- .../components/client-commands/ButtonBox.vue | 58 +++++-- .../val-rewards/AttestationReward.vue | 2 +- .../components/val-rewards/BlockReward.vue | 2 +- .../val-rewards/CommitteeReward.vue | 2 +- .../components/val-rewards/EpochSlot.vue | 4 +- .../components/val-rewards/InQueue.vue | 51 ++++++ .../components/val-rewards/NodeStatus.vue | 18 +++ .../components/val-rewards/OperatorFee.vue | 61 +++++++ .../val-rewards/ParticipationRow.vue | 18 +++ .../components/val-rewards/PerformanceRow.vue | 18 +++ .../components/val-rewards/StatusInfoRow.vue | 9 ++ .../sections/ManagementSection.vue | 13 +- .../staking-page/sections/SidebarSection.vue | 138 ++++++++-------- 18 files changed, 602 insertions(+), 154 deletions(-) create mode 100644 launcher/src/components/UI/staking-page/components/management/components/val-rewards/InQueue.vue create mode 100644 launcher/src/components/UI/staking-page/components/management/components/val-rewards/NodeStatus.vue create mode 100644 launcher/src/components/UI/staking-page/components/management/components/val-rewards/OperatorFee.vue create mode 100644 launcher/src/components/UI/staking-page/components/management/components/val-rewards/ParticipationRow.vue create mode 100644 launcher/src/components/UI/staking-page/components/management/components/val-rewards/PerformanceRow.vue create mode 100644 launcher/src/components/UI/staking-page/components/management/components/val-rewards/StatusInfoRow.vue diff --git a/launcher/public/output.css b/launcher/public/output.css index 3342868d4..ba0cda21f 100755 --- a/launcher/public/output.css +++ b/launcher/public/output.css @@ -2026,6 +2026,10 @@ video { max-height: 100%; } +.max-h-7{ + max-height: 1.75rem; +} + .min-h-11{ min-height: 2.75rem; } @@ -3164,6 +3168,11 @@ video { border-top-right-radius: 0.375rem; } +.rounded-l-full{ + border-top-left-radius: 9999px; + border-bottom-left-radius: 9999px; +} + .rounded-bl-\[37px\]{ border-bottom-left-radius: 37px; } @@ -4149,6 +4158,16 @@ video { background-color: rgb(63 63 70 / var(--tw-bg-opacity)); } +.bg-\[\#2a2c30\]{ + --tw-bg-opacity: 1; + background-color: rgb(42 44 48 / var(--tw-bg-opacity)); +} + +.bg-\[\#464A43\]{ + --tw-bg-opacity: 1; + background-color: rgb(70 74 67 / var(--tw-bg-opacity)); +} + .bg-opacity-80{ --tw-bg-opacity: 0.8; } @@ -4520,6 +4539,10 @@ video { line-height: 1rem; } +.text-\[7px\]{ + font-size: 7px; +} + .font-\[400\]{ font-weight: 400; } @@ -4548,6 +4571,10 @@ video { font-weight: 600; } +.font-thin{ + font-weight: 100; +} + .uppercase{ text-transform: uppercase; } @@ -4794,6 +4821,16 @@ video { color: rgb(234 179 8 / var(--tw-text-opacity)); } +.text-green-300{ + --tw-text-opacity: 1; + color: rgb(134 239 172 / var(--tw-text-opacity)); +} + +.text-amber-100{ + --tw-text-opacity: 1; + color: rgb(254 243 199 / var(--tw-text-opacity)); +} + .placeholder-gray-400\/70::-webkit-input-placeholder{ color: rgb(156 163 175 / 0.7); } diff --git a/launcher/src/components/UI/staking-page/StakingScreen.vue b/launcher/src/components/UI/staking-page/StakingScreen.vue index fc805bb20..019f5639b 100644 --- a/launcher/src/components/UI/staking-page/StakingScreen.vue +++ b/launcher/src/components/UI/staking-page/StakingScreen.vue @@ -1,6 +1,8 @@ @@ -117,7 +127,9 @@ const activeModal = computed(() => { watch( () => serviceStore.installedServices, async () => { - const hasValidator = serviceStore.installedServices.some((s) => s.category === "validator" && s.state === "running"); + const hasValidator = serviceStore.installedServices.some( + (s) => s.category === "validator" && s.state === "running" + ); stakingStore.isStakingDisabled = !hasValidator; } ); @@ -178,24 +190,37 @@ const uploadValidatorKey = (event) => { let uploadedFiles = event.target.files; stakingStore.previewKeys = []; handleFiles(uploadedFiles); - stakingStore.passwordFiles = [...uploadedFiles].filter((file) => file.type === "text/plain"); - stakingStore.keyFiles = [...uploadedFiles].filter((file) => file.type === "application/json"); + stakingStore.passwordFiles = [...uploadedFiles].filter( + (file) => file.type === "text/plain" + ); + stakingStore.keyFiles = [...uploadedFiles].filter( + (file) => file.type === "application/json" + ); stakingStore.isOverDropZone = false; stakingStore.isPreviewListActive = true; stakingStore.setActivePanel("validator"); }; const onDrop = (event) => { - let validator = serviceStore.installedServices.filter((s) => s.category === "validator"); + let validator = serviceStore.installedServices.filter( + (s) => s.category === "validator" + ); if (validator && validator.map((e) => e.state).includes("running")) { stakingStore.previewKeys = []; let droppedFiles = event.dataTransfer.files; - if (droppedFiles[0]["type"] === "application/json" || droppedFiles[0]["type"] === "text/plain") { + if ( + droppedFiles[0]["type"] === "application/json" || + droppedFiles[0]["type"] === "text/plain" + ) { stakingStore.isOverDropZone = false; stakingStore.isPreviewListActive = true; handleFiles(droppedFiles); - stakingStore.passwordFiles = [...droppedFiles].filter((file) => file.type === "text/plain"); - stakingStore.keyFiles = [...droppedFiles].filter((file) => file.type === "application/json"); + stakingStore.passwordFiles = [...droppedFiles].filter( + (file) => file.type === "text/plain" + ); + stakingStore.keyFiles = [...droppedFiles].filter( + (file) => file.type === "application/json" + ); stakingStore.setActivePanel("validator"); } else { stakingStore.inputWrongKey = true; @@ -211,7 +236,9 @@ const onDrop = (event) => { const importKey = async (val) => { stakingStore.importEnteredPassword = val; - stakingStore.importKeyMessage = await ControlService.importKey(stakingStore.selectedValidatorService.config.serviceID); + stakingStore.importKeyMessage = await ControlService.importKey( + stakingStore.selectedValidatorService.config.serviceID + ); stakingStore.isPreviewListActive = false; stakingStore.setActivePanel("insert"); @@ -221,7 +248,10 @@ const importKey = async (val) => { stakingStore.importEnteredPassword = ""; stakingStore.forceRefresh = true; - if (stakingStore.isDoppelgangerProtectionActive && stakingStore.doppelgangerKeys.length > 0) { + if ( + stakingStore.isDoppelgangerProtectionActive && + stakingStore.doppelgangerKeys.length > 0 + ) { setTimeout(() => { stakingStore.setActiveModal(null); }, 10000); @@ -238,7 +268,9 @@ const riskAccepted = async () => { useDeepClone({ serviceID: stakingStore.selectedServiceToFilter.config?.serviceID, url: stakingStore.previewRemoteKeys[0]?.url, //url is for all keys the same - pubkeys: stakingStore.previewRemoteKeys.filter((k) => k.selected).map((k) => k.pubkey), + pubkeys: stakingStore.previewRemoteKeys + .filter((k) => k.selected) + .map((k) => k.pubkey), }) ); } else { @@ -255,13 +287,15 @@ const confirmPassword = async (pass) => { }; const importValidatorProcessing = async () => { - stakingStore.checkActiveValidatorsResponse = await ControlService.checkActiveValidators({ - files: stakingStore.keyFiles, - passwordFiles: stakingStore.passwordFiles, - password: stakingStore.importEnteredPassword, - serviceID: stakingStore.selectedValidatorService.config?.serviceID, - slashingDB: stakingStore.slashingDB?.path || null, - }); + stakingStore.checkActiveValidatorsResponse = await ControlService.checkActiveValidators( + { + files: stakingStore.keyFiles, + passwordFiles: stakingStore.passwordFiles, + password: stakingStore.importEnteredPassword, + serviceID: stakingStore.selectedValidatorService.config?.serviceID, + slashingDB: stakingStore.slashingDB?.path || null, + } + ); if ( stakingStore.checkActiveValidatorsResponse.length === 0 || @@ -421,7 +455,9 @@ const removeGroupConfirm = async (item) => { // stakingStore.currentGroup.keys.forEach((key) => { // stakingStore.keys.push(key); // }); - stakingStore.validatorKeyGroups = stakingStore.validatorKeyGroups.filter((group) => group?.id !== item.id); + stakingStore.validatorKeyGroups = stakingStore.validatorKeyGroups.filter( + (group) => group?.id !== item.id + ); stakingStore.setActiveModal(null); stakingStore.setMode("create"); stakingStore.currentGroup = ""; @@ -436,7 +472,9 @@ const removeGroupConfirm = async (item) => { //Confirm Rename Validator Key const confirmValidatorKeyRename = async (name) => { - stakingStore.keys.find((key) => key.key === stakingStore.selectKeyToRename.key).selected = false; + stakingStore.keys.find( + (key) => key.key === stakingStore.selectKeyToRename.key + ).selected = false; let el = stakingStore.selectKeyToRename; el.displayName = name; @@ -462,7 +500,9 @@ const resetValidatorKeyName = async (el) => { console.log("Couldn't Reset Key Name!"); } - stakingStore.keys.find((key) => key.key === stakingStore.selectKeyToRename.key).selected = false; + stakingStore.keys.find( + (key) => key.key === stakingStore.selectKeyToRename.key + ).selected = false; }; //****End of Validator Key **** @@ -472,10 +512,16 @@ const doppelgangerController = async (item) => { try { const res = await ControlService.getServiceYAML(item?.config.serviceID); item.expertOptions.map((option) => { - if (item.service === "LighthouseValidatorService" && option.title === "Doppelganger") { - stakingStore.doppelgangerStatus = res.indexOf(option.pattern[0]) === -1 ? false : true; + if ( + item.service === "LighthouseValidatorService" && + option.title === "Doppelganger" + ) { + stakingStore.doppelgangerStatus = + res.indexOf(option.pattern[0]) === -1 ? false : true; } else if (option.title === "Doppelganger") { - const matchedValue = res.match(new RegExp(option.pattern[0])) ? [...res.match(new RegExp(option.pattern[0]))][2] : ""; + const matchedValue = res.match(new RegExp(option.pattern[0])) + ? [...res.match(new RegExp(option.pattern[0]))][2] + : ""; stakingStore.doppelgangerStatus = matchedValue === "true" ? true : false; stakingStore.isDoppelgangerProtectionActive = true; @@ -507,8 +553,12 @@ const pickValidatorService = async (service) => { //Delete Preview Key const deletePreviewKey = async (item) => { - stakingStore.previewKeys = stakingStore.previewKeys.filter((key) => key.filename !== item.filename); - stakingStore.doppelgangerKeys = stakingStore.doppelgangerKeys.filter((key) => key.filename !== item.filename); + stakingStore.previewKeys = stakingStore.previewKeys.filter( + (key) => key.filename !== item.filename + ); + stakingStore.doppelgangerKeys = stakingStore.doppelgangerKeys.filter( + (key) => key.filename !== item.filename + ); const indexItem = stakingStore.keyFiles.findIndex((key) => key.name === item.filename); if (indexItem !== -1) { @@ -594,7 +644,10 @@ const withdrawValidatorKey = async () => { // If multiple keys const multiKeys = stakingStore.keys - .filter((item) => item.validatorID === stakingStore.selectedServiceToFilter.config?.serviceID) + .filter( + (item) => + item.validatorID === stakingStore.selectedServiceToFilter.config?.serviceID + ) .map((item) => item.key); res = await Promise.all( @@ -681,7 +734,10 @@ const exportExitMessage = async () => { saveExitMessage(result, "single"); } else { const pubkeys = stakingStore.keys - .filter((item) => item.validatorID === stakingStore.selectedServiceToFilter?.config?.serviceID) + .filter( + (item) => + item.validatorID === stakingStore.selectedServiceToFilter?.config?.serviceID + ) .map((item) => item.key); const results = await Promise.all( @@ -701,9 +757,13 @@ const exportExitMessage = async () => { }; const saveExitMessage = (data, type) => { - const content = type === "single" ? JSON.stringify(data, null, 2) : data.map((entry) => JSON.stringify(entry, null, 2)).join("\n\n"); + const content = + type === "single" + ? JSON.stringify(data, null, 2) + : data.map((entry) => JSON.stringify(entry, null, 2)).join("\n\n"); - const fileName = type === "single" ? "single_exit_message.txt" : "multiple_exit_messages.txt"; + const fileName = + type === "single" ? "single_exit_message.txt" : "multiple_exit_messages.txt"; const blob = new Blob([content], { type: "application/json;charset=utf-8" }); saveAs(blob, fileName); }; @@ -734,7 +794,11 @@ const removeValidatorKeys = async () => { if (changed === 1 && id) { // Remove all Local Keys if selected validator holds some if (localKeys && localKeys.length > 0) { - const returnVal = await deleteValidators(id, localKeys, stakingStore.pickedSlashing); + const returnVal = await deleteValidators( + id, + localKeys, + stakingStore.pickedSlashing + ); if (stakingStore.pickedSlashing === "yes") { downloadFile(returnVal); @@ -778,7 +842,9 @@ const getKeySetupColor = () => { try { stakingStore.keys = stakingStore?.keys.map((key) => { const allSetups = useDeepClone(setupStore.allSetups); - const setup = allSetups.find((s) => s?.services.some((service) => service.id === key.validatorID)); + const setup = allSetups.find((s) => + s?.services.some((service) => service.id === key.validatorID) + ); const setupColor = setup ? setup.color : "default"; return { ...key, @@ -810,11 +876,15 @@ const importRemoteKey = async (args) => { const confirmImportRemoteKeys = async () => { stakingStore.setActiveModal("remote"); - stakingStore.checkActiveValidatorsResponse = await ControlService.checkActiveValidators({ - files: stakingStore.previewRemoteKeys.filter((k) => k.selected).map((k) => k.pubkey), - serviceID: stakingStore.selectedServiceToFilter.config?.serviceID, - isRemote: true, - }); + stakingStore.checkActiveValidatorsResponse = await ControlService.checkActiveValidators( + { + files: stakingStore.previewRemoteKeys + .filter((k) => k.selected) + .map((k) => k.pubkey), + serviceID: stakingStore.selectedServiceToFilter.config?.serviceID, + isRemote: true, + } + ); stakingStore.setActivePanel(null); if ( stakingStore.checkActiveValidatorsResponse.length === 0 || @@ -824,7 +894,9 @@ const confirmImportRemoteKeys = async () => { useDeepClone({ serviceID: stakingStore.selectedServiceToFilter.config?.serviceID, url: stakingStore.previewRemoteKeys[0]?.url, - pubkeys: stakingStore.previewRemoteKeys.filter((k) => k.selected).map((k) => k.pubkey), + pubkeys: stakingStore.previewRemoteKeys + .filter((k) => k.selected) + .map((k) => k.pubkey), }) ); diff --git a/launcher/src/components/UI/staking-page/components/list/ListBody.vue b/launcher/src/components/UI/staking-page/components/list/ListBody.vue index 4caee834b..2f7f676ac 100644 --- a/launcher/src/components/UI/staking-page/components/list/ListBody.vue +++ b/launcher/src/components/UI/staking-page/components/list/ListBody.vue @@ -34,16 +34,16 @@ import { ref, computed, watchEffect, watch, onMounted, onUnmounted } from 'vue'; > {{ $t("stakingPage.noVal") }} - {{ + {{ $t("stakingPage.noMatch") }} @@ -68,7 +68,7 @@ import { ref, computed, watchEffect, watch, onMounted, onUnmounted } from 'vue'; { if (stakingStore.searchContent === "") { return stakingStore.keys; } + return stakingStore.keys.filter( (key) => (key.key && key.key.toLowerCase().includes(stakingStore.searchContent.toLowerCase())) || - (key.displayName && key.displayName !== "" && key.displayName.toLowerCase().includes(stakingStore.searchContent.toLowerCase())) + (key.displayName && key.displayName !== "" && key.displayName.toLowerCase()?.includes(stakingStore.searchContent.toLowerCase())) ); }); +console.log(stakingStore.filteredKeys); +console.log("All keys", stakingStore.keys); + const getFilteredValidators = computed(() => { + stakingStore.selectedServiceToFilter; if (!setupStore.selectedSetup) { // If selectedSetup is null, return all keys return stakingStore.filteredKeys; } else { - const serviceIds = setupStore.selectedSetup.services.map((service) => service.config.serviceID); + const serviceIds = setupStore.selectedSetup.services?.map((service) => service.config.serviceID); // Filter keys by checking if validatorID exists in serviceIds - return stakingStore.filteredKeys.filter((key) => serviceIds.includes(key.validatorID)); + return stakingStore.filteredKeys.filter((key) => serviceIds?.includes(key.validatorID)); } }); diff --git a/launcher/src/components/UI/staking-page/components/list/rows/KeyRow.vue b/launcher/src/components/UI/staking-page/components/list/rows/KeyRow.vue index 0e1b89af2..301385b18 100644 --- a/launcher/src/components/UI/staking-page/components/list/rows/KeyRow.vue +++ b/launcher/src/components/UI/staking-page/components/list/rows/KeyRow.vue @@ -2,10 +2,15 @@ import { computed } from 'vue';