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';
-
+
-
+
- {{
- displayText
- }}
+ {{ displayText }}
{{ props.item.balance }}
-
+
@@ -85,7 +106,7 @@ import { computed } from 'vue';
/>
@@ -98,7 +119,7 @@ import { computed } from 'vue';
/>
@@ -111,6 +132,11 @@ import { computed } from 'vue';
/>
@@ -137,6 +164,11 @@ import { computed } from 'vue';
/>
{
return formattedPubKey.value; // Assuming formattedPubKey is the formatted version of the public key
});
+const getValidatorClients = computed(() => {
+ const clients = serviceStore.installedServices
+ .filter((service) => service.category === "validator")
+ .find((service) => service.config.serviceID === props.item.validatorID);
+ return clients;
+});
+
//Methods
+
const navToBeaconcha = (network) => {
const urls = {
gnosis: "https://gnosischa.in/",
diff --git a/launcher/src/components/UI/staking-page/components/management/ValidatorRewards.vue b/launcher/src/components/UI/staking-page/components/management/ValidatorRewards.vue
index 3ba7a8b66..ed47af6a0 100644
--- a/launcher/src/components/UI/staking-page/components/management/ValidatorRewards.vue
+++ b/launcher/src/components/UI/staking-page/components/management/ValidatorRewards.vue
@@ -1,14 +1,82 @@
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/launcher/src/components/UI/staking-page/components/management/components/client-commands/ButtonBox.vue b/launcher/src/components/UI/staking-page/components/management/components/client-commands/ButtonBox.vue
index b4708b7c2..44c57986d 100644
--- a/launcher/src/components/UI/staking-page/components/management/components/client-commands/ButtonBox.vue
+++ b/launcher/src/components/UI/staking-page/components/management/components/client-commands/ButtonBox.vue
@@ -1,7 +1,10 @@
-
+
+
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/AttestationReward.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/AttestationReward.vue
index 8da16eeb4..50777c333 100644
--- a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/AttestationReward.vue
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/AttestationReward.vue
@@ -1,6 +1,6 @@
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/BlockReward.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/BlockReward.vue
index c43d9a8dc..885f9ea5e 100644
--- a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/BlockReward.vue
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/BlockReward.vue
@@ -1,6 +1,6 @@
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/CommitteeReward.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/CommitteeReward.vue
index a8a6dddb8..6dc1098e3 100644
--- a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/CommitteeReward.vue
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/CommitteeReward.vue
@@ -1,6 +1,6 @@
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/EpochSlot.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/EpochSlot.vue
index 7e08293a2..defe15c8d 100644
--- a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/EpochSlot.vue
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/EpochSlot.vue
@@ -1,7 +1,5 @@
-
+
+
+
+
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/NodeStatus.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/NodeStatus.vue
new file mode 100644
index 000000000..479ee8994
--- /dev/null
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/NodeStatus.vue
@@ -0,0 +1,18 @@
+
+
+
+ NODE STATUS
+
+
+ ACTIVE
+
+
+
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/OperatorFee.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/OperatorFee.vue
new file mode 100644
index 000000000..6463ac7c6
--- /dev/null
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/OperatorFee.vue
@@ -0,0 +1,61 @@
+
+
+
+ OPERATOR FEE
+
+
+ 4 SSV
+
+
+
+
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/ParticipationRow.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/ParticipationRow.vue
new file mode 100644
index 000000000..49e1782fc
--- /dev/null
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/ParticipationRow.vue
@@ -0,0 +1,18 @@
+
+
+
+ PARTICIPATION
+
+
+ 4/4
+
+
+
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/PerformanceRow.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/PerformanceRow.vue
new file mode 100644
index 000000000..8cffd02fe
--- /dev/null
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/PerformanceRow.vue
@@ -0,0 +1,18 @@
+
+
+
+ PERFORMANCE
+
+
+ 100%
+
+
+
diff --git a/launcher/src/components/UI/staking-page/components/management/components/val-rewards/StatusInfoRow.vue b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/StatusInfoRow.vue
new file mode 100644
index 000000000..053ce5b00
--- /dev/null
+++ b/launcher/src/components/UI/staking-page/components/management/components/val-rewards/StatusInfoRow.vue
@@ -0,0 +1,9 @@
+
+
+ STATUS INFO
+
+
diff --git a/launcher/src/components/UI/staking-page/sections/ManagementSection.vue b/launcher/src/components/UI/staking-page/sections/ManagementSection.vue
index 62d362c06..7a607eae2 100644
--- a/launcher/src/components/UI/staking-page/sections/ManagementSection.vue
+++ b/launcher/src/components/UI/staking-page/sections/ManagementSection.vue
@@ -2,17 +2,26 @@
-
+