Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADD: multi setups (configs) #1809

Merged
merged 224 commits into from
Jun 26, 2024
Merged
Changes from all commits
Commits
Show all changes
224 commits
Select commit Hold shift + click to select a range
269c803
init
gbayasgalan Apr 2, 2024
fa9be61
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 4, 2024
4a98772
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 4, 2024
645faa4
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 4, 2024
cddf0ed
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 4, 2024
597a700
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 10, 2024
83f9950
ADD: read multi config file
gbayasgalan Apr 10, 2024
b3a3288
ADD: multi config store
MaxTheGeeek Apr 10, 2024
7602cdc
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Apr 10, 2024
75e3b5f
FIX: reading sshService
gbayasgalan Apr 10, 2024
9ab4ca3
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
gbayasgalan Apr 10, 2024
1f2ce38
ADD: read config in node page
MaxTheGeeek Apr 10, 2024
64000dc
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Apr 10, 2024
6b39779
FIX: riny fix to read multi config file
gbayasgalan Apr 10, 2024
8b0c383
REMOVE: logs
MaxTheGeeek Apr 10, 2024
fa65ba9
ADD: multi configs compables
MaxTheGeeek Apr 10, 2024
34bb7ca
REFACTOR: multiconfig data structure
gbayasgalan Apr 11, 2024
043292d
ADD: init node page new layout for configs
MaxTheGeeek Apr 11, 2024
e67dfc0
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Apr 11, 2024
3c4556c
CHANGE: path for clients
MaxTheGeeek Apr 11, 2024
a60b48b
REFACTOR: setups structure in FE
MaxTheGeeek Apr 11, 2024
ea61d6b
ADD: setup layout & structure for node page
MaxTheGeeek Apr 11, 2024
e20b9f6
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 12, 2024
8a3c2a6
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 14, 2024
471ff3f
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 16, 2024
1d08d6d
ADD: single setup
MaxTheGeeek Apr 16, 2024
122de00
ADD: setup dropdown
MaxTheGeeek Apr 16, 2024
b7f4c43
ADD: rename on edit page
MaxTheGeeek Apr 16, 2024
96d7287
ADD: config color to list
MaxTheGeeek Apr 16, 2024
7da5411
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Apr 16, 2024
3a50f9d
ADD: gradien bg
MaxTheGeeek Apr 17, 2024
64e5b83
CHANGE: gradient
MaxTheGeeek Apr 17, 2024
3e25e78
ADD: server view page
MaxTheGeeek Apr 17, 2024
aa46427
CHANGE: service title
MaxTheGeeek Apr 17, 2024
74b5264
ADD: multi configs on node page
MaxTheGeeek Apr 17, 2024
d66de2d
REMOVE: useless
MaxTheGeeek Apr 17, 2024
cfb56d0
ADD: multi configs colors
MaxTheGeeek Apr 17, 2024
bd24159
ADD: default bg & text color
MaxTheGeeek Apr 17, 2024
7c91f50
ADD: edit server view structure
MaxTheGeeek Apr 18, 2024
9b1b5c5
REFACTOR: setup & client styles
MaxTheGeeek Apr 18, 2024
76ad6f0
ADD: dynamic color to setup & configs
MaxTheGeeek Apr 18, 2024
e320ee2
Merge branch 'main' into pr/1809
MaxTheGeeek Apr 18, 2024
26ac6f6
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 19, 2024
b3f758a
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 23, 2024
c57903b
ADD:setups dropdown & fix issues
MaxTheGeeek Apr 24, 2024
fdcb37a
ADD: node & edit server view
MaxTheGeeek Apr 24, 2024
8bcd0ec
FIX: selecting & opening setups & config on edit
MaxTheGeeek Apr 24, 2024
e83d201
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Apr 24, 2024
4fa5f6c
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 25, 2024
98e5c30
CHANGE: setups styles
MaxTheGeeek Apr 25, 2024
500c518
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Apr 25, 2024
0fbc8bd
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 25, 2024
5fdb487
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 26, 2024
8028608
ADD: services: add, remove, switch. setups: exportAll, delete. multiS…
gbayasgalan Apr 26, 2024
aa92efa
FIX: fix for jest test
gbayasgalan Apr 26, 2024
7e535d4
FIX: jest test
gbayasgalan Apr 26, 2024
54443a7
fix: some improvement
gbayasgalan Apr 26, 2024
28b56b5
ADD: get and setSetup
gbayasgalan Apr 29, 2024
2961806
CHANGE: setup types based on the network
MaxTheGeeek Apr 29, 2024
3804444
ADD: drawer as a parent compoent
MaxTheGeeek Apr 29, 2024
59d082a
ADD: setup drawer first layout
MaxTheGeeek Apr 29, 2024
9c99a53
REMOVE: useless flags
MaxTheGeeek Apr 29, 2024
8381f99
CHANGE: setup drawer bg
MaxTheGeeek Apr 29, 2024
06dacb8
ADD: services drawer
MaxTheGeeek Apr 29, 2024
f6bac14
ADD: external round icons
MaxTheGeeek Apr 29, 2024
86d3e19
ADD:import setups button & modal
MaxTheGeeek Apr 30, 2024
68206a1
FIX: import config data structure
MaxTheGeeek Apr 30, 2024
0f8ca84
ADD: multisetup import - all
gbayasgalan Apr 30, 2024
20662cd
ADD: setups to import configs list
MaxTheGeeek Apr 30, 2024
21ba541
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Apr 30, 2024
4f0a22b
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Apr 30, 2024
eb483f0
ADD: create setup drawer and modal
MaxTheGeeek May 2, 2024
973e6e1
FIX: create setup modal
MaxTheGeeek May 2, 2024
84dbfdb
ADD: create/delete Setup
gbayasgalan May 3, 2024
a5f95ed
FIX: jest test
gbayasgalan May 3, 2024
c333fad
ADD: add server services to drawer
MaxTheGeeek May 3, 2024
030f103
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan May 7, 2024
e529a53
FIX: common services UI issues & removing from server
MaxTheGeeek May 7, 2024
8c81e0d
FIX: exclude common servises from total network
MaxTheGeeek May 7, 2024
39705b9
FIX: exclude common services from setups dropdown
MaxTheGeeek May 7, 2024
cc3a454
ADD: open btn to setups menu on edit
MaxTheGeeek May 7, 2024
0604ba2
REFACTOR: getting installed clients based on the setups services
MaxTheGeeek May 7, 2024
cf7fa64
CHANGE: styles
MaxTheGeeek May 7, 2024
346cb94
REMOVE: useless
MaxTheGeeek May 7, 2024
0bf93e7
ADD: smooth release pages
MaxTheGeeek May 7, 2024
96ed231
FIX: icon src typo
MaxTheGeeek May 7, 2024
939eb38
Add: dynmic network by stups
MaxTheGeeek May 7, 2024
a2289f4
REMOVE: logs
MaxTheGeeek May 7, 2024
15261b8
FIX: icon not found
MaxTheGeeek May 7, 2024
c656b07
FIX: drawer handlers & services filter
MaxTheGeeek May 10, 2024
ec1135f
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek May 10, 2024
d06b4d4
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan May 10, 2024
0ac770f
FIX: service drawer filtering
MaxTheGeeek May 10, 2024
1c1f86c
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek May 10, 2024
2eeb5c6
FIX: service drawer
MaxTheGeeek May 10, 2024
e704188
ADD: create setup modal
MaxTheGeeek May 10, 2024
a15d29b
FIX: create & delete setups reactivity
MaxTheGeeek May 10, 2024
857f127
ADD: setup infos modal
MaxTheGeeek May 10, 2024
20c3089
FIX: color picker
MaxTheGeeek May 10, 2024
9952db0
ADD: init rename function
gbayasgalan May 13, 2024
07375eb
ADD: rename setup
gbayasgalan May 13, 2024
0d8411c
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan May 13, 2024
b3474ee
ADD: error handler into rename setup fn
gbayasgalan May 13, 2024
516e763
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
gbayasgalan May 13, 2024
bc699b6
ADD: rename setup functionality
MaxTheGeeek May 13, 2024
7694cb7
REMOVE: useless emits
MaxTheGeeek May 13, 2024
1962df1
Merge branch 'main' into multi-config
MaxTheGeeek May 14, 2024
a98b37b
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek May 14, 2024
1415e95
FIX: rename reactivity
MaxTheGeeek May 14, 2024
99059ec
ADD: remove setup to changes panel
MaxTheGeeek May 14, 2024
b6103f4
ADD: create setup real time reactivity
MaxTheGeeek May 14, 2024
b166343
Merge branch 'main' into multi-config
MaxTheGeeek May 14, 2024
44fc9c3
ADD: subtasks for delete setups
MaxTheGeeek May 14, 2024
e210926
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek May 14, 2024
de6c877
ADD: remove setup services
MaxTheGeeek May 16, 2024
aa8a954
REFACTOR: simplify confirm handler
MaxTheGeeek May 16, 2024
30ed17b
REMOVE: logs
MaxTheGeeek May 16, 2024
dc83f11
FIX: some bugs
MaxTheGeeek May 16, 2024
aaa0f63
Merge branch 'main' into multi-config
gbayasgalan May 16, 2024
f35e89c
FIX: conflict
gbayasgalan May 16, 2024
ce43f6c
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan May 16, 2024
ba2ab97
REFACTOR: backend fn
gbayasgalan May 16, 2024
f2d548a
ADD: export a setup
gbayasgalan May 17, 2024
4ad0027
ADD: export single setup to node
MaxTheGeeek May 17, 2024
c7d614a
ADD: export single setup
gbayasgalan May 17, 2024
53d90bc
ADD: export & save single setup
MaxTheGeeek May 17, 2024
2090f77
FIX: switch client
gbayasgalan May 17, 2024
d805a61
ADD: install common services
MaxTheGeeek May 21, 2024
39c2bad
ADD: init - import single Setup backend
gbayasgalan May 21, 2024
5a5c151
ADD: single setup import
gbayasgalan May 22, 2024
29e78df
REFACTOR: filter styles
MaxTheGeeek May 22, 2024
3447281
FIX: all services filter
MaxTheGeeek May 22, 2024
bedb51c
ADD: single setup import
MaxTheGeeek May 22, 2024
77c9448
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek May 22, 2024
8074bcf
FIX: drawer services filtering
MaxTheGeeek May 22, 2024
2d40b4b
ADD: custom loader animation in edit page
MaxTheGeeek May 22, 2024
2927033
ADD: loader anime for import setup
MaxTheGeeek May 22, 2024
d5b8b72
REFACTOR: single setup import (update path as current one)
gbayasgalan May 22, 2024
db9988d
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
gbayasgalan May 22, 2024
283129f
FIX: port conflict during the importing single setup
gbayasgalan May 22, 2024
6d0a33a
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan May 22, 2024
9357cb6
CHANGE: dblclick to click
MaxTheGeeek May 23, 2024
d3f4f8d
ADD: init - check node connection
gbayasgalan May 28, 2024
71ec54c
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
gbayasgalan May 28, 2024
8cbf087
ADD: check connection service background
gbayasgalan May 28, 2024
d81047f
REFACTOR: checking connection quality
gbayasgalan May 29, 2024
9ec2a1b
REFACTOR: check connection fn
gbayasgalan May 29, 2024
55a1b84
ADD: ping quality composables
MaxTheGeeek May 29, 2024
e19ca25
ADD: connection status
MaxTheGeeek May 29, 2024
4a348ce
ADD: dynamic ping quality
MaxTheGeeek May 31, 2024
dfeced7
Merge branch 'main' into multi-config
gbayasgalan May 31, 2024
43b3efc
FIX: conflict
gbayasgalan May 31, 2024
4ba244c
REFACTOR: checking connection quality fn
gbayasgalan May 31, 2024
8817b00
ADD: ping status to edit page
MaxTheGeeek May 31, 2024
b7984f3
ADD: alert for poor network
MaxTheGeeek May 31, 2024
f696451
CHANGE: wifi sign border
MaxTheGeeek May 31, 2024
df175ab
Merge branch 'main' into multi-config
mabasian Jun 4, 2024
6305114
ADD: deropdown to the control screen
mabasian Jun 4, 2024
50bfade
FIX: dropdown in the control page
mabasian Jun 4, 2024
be3ceb9
FIX: services in control plugin shows the config services
mabasian Jun 4, 2024
8ac81d1
ADD: footer connection status
MaxTheGeeek Jun 4, 2024
924bfdf
UPDATE: simplify setup changes
MaxTheGeeek Jun 4, 2024
5d0f34c
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Jun 4, 2024
dce1bae
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Jun 5, 2024
df8bca5
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Jun 5, 2024
c05994a
FIX: single import fn
gbayasgalan Jun 5, 2024
9085c6d
ADD: setup id filter to clients
MaxTheGeeek Jun 5, 2024
f4bc772
ADD: setup id filter to services
MaxTheGeeek Jun 5, 2024
cf796b2
Merge branch 'main' into multi-config
gbayasgalan Jun 5, 2024
a91f7aa
ADD: selecting setup to composable
MaxTheGeeek Jun 6, 2024
e29207a
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Jun 6, 2024
6b8c185
FIX: multi setup installing reactivity
MaxTheGeeek Jun 10, 2024
4f71c3e
ADD: updated setups after installig services
MaxTheGeeek Jun 11, 2024
a90f0aa
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Jun 12, 2024
000bb70
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Jun 12, 2024
d1e1a30
ADD: gradient bg to sidebar
MaxTheGeeek Jun 12, 2024
6134a67
ADD: connect clients on same setup
MaxTheGeeek Jun 12, 2024
7c7ce5f
UPDATE: setup drawer
MaxTheGeeek Jun 12, 2024
4f47289
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Jun 12, 2024
12a84e0
ADD: switch setup's network
gbayasgalan Jun 12, 2024
9757868
FIX: some fixs
MaxTheGeeek Jun 12, 2024
4794df0
REMOVE: useless scripts
MaxTheGeeek Jun 12, 2024
b48c58b
ADD: condition on network switch
MaxTheGeeek Jun 12, 2024
1a4dc65
REMOVE: logs
MaxTheGeeek Jun 12, 2024
abc3bd9
CHANGE: v-for unique key
MaxTheGeeek Jun 12, 2024
253c480
FIX: confirm handler for all tasks
MaxTheGeeek Jun 12, 2024
5937b6e
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Jun 12, 2024
08b6074
UPDATE: staking sidebar gradient
MaxTheGeeek Jun 13, 2024
7976686
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Jun 13, 2024
7136136
UPDATE: compatible size
MaxTheGeeek Jun 13, 2024
05cf3a6
CHANGE: size and number of rows
MaxTheGeeek Jun 13, 2024
4455359
ADD: staking header
MaxTheGeeek Jun 13, 2024
4284b89
CHANGE: moved total balance to staking header
MaxTheGeeek Jun 13, 2024
4205531
ADD: setup dropdown & network status to staking header
MaxTheGeeek Jun 13, 2024
6a7c0a7
REFACTOR: computed
MaxTheGeeek Jun 13, 2024
f2f2b67
ADD: staking header to list section
MaxTheGeeek Jun 13, 2024
1a8f375
UPDATE: control screen items
mabasian Jun 13, 2024
543a5e5
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Jun 14, 2024
a443a78
UPDATE: peer to peer widget to multi config
mabasian Jun 14, 2024
ab35609
FIX: syncStatus synced with the setupConfig
mabasian Jun 15, 2024
123d56d
Merge branch 'main' into multi-config
mabasian Jun 19, 2024
d13494a
FIX: RPC endpoint synced with the selected setup
mabasian Jun 19, 2024
1ecaae3
ADD: filter by selected setup
MaxTheGeeek Jun 19, 2024
94b28fd
FIX: disabled image & skeletons size
MaxTheGeeek Jun 19, 2024
9f65d8a
FIX: wifi sign size
MaxTheGeeek Jun 19, 2024
403eba5
UPDATE: staking page
MaxTheGeeek Jun 19, 2024
fb49335
REMOVE: goerli
MaxTheGeeek Jun 19, 2024
3847061
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Jun 19, 2024
650acf8
FIX: WS endpoint synced with the selected setup
mabasian Jun 19, 2024
c0b5af5
FIX: data api synced with the selected setup
mabasian Jun 19, 2024
62138a7
update: clear the logs and comment
mabasian Jun 19, 2024
7637bb6
Merge branch 'stereum-dev:main' into multi-config
gbayasgalan Jun 20, 2024
3d52ee4
FIX: undefined item
MaxTheGeeek Jun 24, 2024
d72f3e2
Merge branch 'multi-config' of https://github.com/gbayasgalan/ethereu…
MaxTheGeeek Jun 24, 2024
ced8506
ADD: filtering keys by setup
MaxTheGeeek Jun 25, 2024
f966f8b
FIX: reactivity after installing a client
MaxTheGeeek Jun 26, 2024
5103b27
FIX: wifi warning
MaxTheGeeek Jun 26, 2024
da8c31d
ADD: update to multisetup
gbayasgalan Jun 26, 2024
ce80bf1
ADD: function upgrade to multi setups
MaxTheGeeek Jun 26, 2024
8675e98
REMOVE: network modal
MaxTheGeeek Jun 26, 2024
35a0e9f
FIX: some fixs
MaxTheGeeek Jun 26, 2024
0289f6b
FIX: staking screen height
MaxTheGeeek Jun 26, 2024
7cab34b
REMOVE: logs
MaxTheGeeek Jun 26, 2024
363718f
IMPROVE: error handling for check & create MultiSetup fn
gbayasgalan Jun 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions launcher/package.json
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@
"geoip-lite": "^1.4.7",
"jszip": "^3.10.1",
"leader-line-new": "^1.1.9",
"ping": "^0.4.4",
"pinia": "^2.0.33",
"qrcode": "^1.5.1",
"semver": "^7.6.0",
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added launcher/public/animation/setup/loader.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added launcher/public/img/icon/edit-node-icons/link.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
374 changes: 317 additions & 57 deletions launcher/public/output.css

Large diffs are not rendered by default.

364 changes: 364 additions & 0 deletions launcher/src/backend/ConfigManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
const log = require("electron-log");
const yaml = require("js-yaml");
const uuid = require("uuid");

export class ConfigManager {
constructor(nodeConnection) {
this.nodeConnection = nodeConnection;
this.serviceManager = null;
this.multiSetupPath = "/etc/stereum/multisetup.yaml";
this.commonServices = [
"PrometheusService",
"GrafanaService",
"PrometheusNodeExporterService",
"NotificationService",
];
}
setServiceManager(serviceManager) {
this.serviceManager = serviceManager;
}

/**
* Checks if the multi-setup file exists, reads all installed services if not, and creates a multi-setup.yaml file.
*/
async checkAndCreateMultiSetup() {
try {
const fileExistResult = await this.checkFileExistence();
if (!fileExistResult || !fileExistResult.stdout) {
throw new Error("Invalid response from checkFileExistence");
}

const services = await this.serviceManager.readServiceConfigurations();
if (!Array.isArray(services)) {
throw new Error("Invalid response from readServiceConfigurations");
}

if (fileExistResult.stdout.includes("notExist")) {
if (services.length === 0) {
await this.createMultiSetupYaml({}, "");
} else if (services.length > 0) {
const network = services[0]?.network;
if (!network) {
throw new Error("Service configuration is missing network information");
}
await this.createMultiSetupYaml(services, network);
}
}
} catch (error) {
console.error("Error in checkAndCreateMultiSetup:", error.message, error.stack);
}
}

/**
* Asynchronously checks if a specific file exists on a remote system using SSH.
* Executes 'test -f' to determine file presence, echoing "exist" or "notExist".
*
* @returns {Promise<Object>} Resolves with command execution result, indicating file existence.
*/
async checkFileExistence() {
const fileExistResult = await this.nodeConnection.sshService.exec(
`test -f ${this.multiSetupPath} && echo "exist" || echo "notExist"`
);
return fileExistResult;
}

/**
* Creates a multi-setup configuration file in YAML format.
*
* @param {Object|string} services - The configurations to include in the setup. If a string is provided,
* it is treated as a single configuration ID. If an object is provided, it should map configuration IDs
* to configuration objects.
* @param {string} network - The network of the setup.
*/
async createMultiSetupYaml(services, network) {
try {
// Check if the multiSetup configuration file exists
const fileExistResult = await this.checkFileExistence();

// If the file does not exist, create it
if (fileExistResult.stdout.includes("notExist")) {
await this.nodeConnection.sshService.exec(`touch ${this.multiSetupPath}`);
}

if (!Array.isArray(services)) {
services = [];
}

await this.createMultiSetupContent(services, network);
} catch (error) {
console.error(`Failed to create multi-setup configuration file in YAML format: ${error}`);
}
}

/**
* Creates the content for a multi-setup configuration file.
* @param {Object|string} services - The configurations to include in the setup.
* @param {string} network - The network of the setup.
* @returns {Promise<void>} - does not return anything.
*/
async createMultiSetupContent(services, network) {
let setupServicesObj = await this.createSetupContent(services, network);
let commonServicesObj = await this.createCommonContent(services);
let setups = { ...setupServicesObj, ...commonServicesObj };
await this.writeMultiSetup(setups);
}

/**
* Creates the content for a setup
*
* @param {Array} services - The services to include in the setup.
* @param {string} network - The network of the setup.
* @returns {Object} - An object representing the setup configuration.
*/
async createSetupContent(services, network) {
const setupServices = services.filter((item) => !this.commonServices.includes(item.service));
let setupServicesObj = {};
if (setupServices.length > 0) {
let setupId = uuid.v4();
setupServicesObj = {
[setupId]: {
name: "setup1",
network: network,
color: "default",
type: "ETH",
services: setupServices.map((service) => service.id),
},
};
}
return setupServicesObj;
}

/**
* Creates the content for a common setup
*
* @param {Array} services - The services to include in the setup.
* @returns {Promise<Object>} - An object representing the common services setup configuration.
*/
async createCommonContent(services) {
const commonServices = services.filter((item) => this.commonServices.includes(item.service));
let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);

let setupId;
let commonServicesObj;

// Check if a commonServices setup already exists
for (let key in setupsObj) {
if (setupsObj[key].name === "commonServices") {
setupId = key;
commonServicesObj = { [setupId]: setupsObj[key] };
break;
}
}

// If no commonServices setup exists, create a new one
if (!commonServicesObj) {
setupId = uuid.v4();
commonServicesObj = {
[setupId]: {
name: "commonServices",
network: "default",
color: "default",
type: "default",
services: [],
},
};
}

if (commonServices.length > 0) {
commonServicesObj[setupId].services = commonServicesObj[setupId].services.concat(
commonServices.map((service) => service.id)
);
}
return commonServicesObj;
}

/**
* Writes a setup to the multi-setup configuration file.
*
* @param {Object} setup - The setup to write to the multi-setup configuration file.
*/
async writeMultiSetup(setup) {
try {
// Convert the setup object to a YAML string and escape backticks
let setupYaml = yaml.safeDump(setup).replace(/`/g, "\\`");

await this.nodeConnection.sshService.exec(`echo -e "${setupYaml}" > ${this.multiSetupPath}`);
} catch (error) {
console.error(`Failed to write setup to multi-setup configuration file: ${error}`);
}
}

/**
* Reads the multi-setup configuration file.
*
* @returns {Promise<string|undefined>} The content of the multi-setup configuration file,
* or undefined if an error occurs.
*/
async readMultiSetup() {
try {
let result = await this.nodeConnection.sshService.exec(`cat ${this.multiSetupPath}`);
if (result) {
return result.stdout;
} else {
log.error("Result is undefined");
return undefined;
}
} catch (err) {
log.error("Can't read multiConfig file", err);
return undefined;
}
}

/**
* Renames an existing setup.
*
* @param {Object} setup - The setup object.
* @param {string} setup.setupId - The id of the setup to rename.
* @param {string} setup.setupName - The new name for the setup.
*/
async renameSetup(setup) {
try {
let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);

if (setupsObj[setup.setupId]) {
setupsObj[setup.setupId].name = setup.setupName;
} else {
throw new Error(`Setup with id ${setup.setupId} not found.`);
}

await this.writeMultiSetup(setupsObj);
} catch (error) {
console.error(`Failed to rename setup: ${error.message}`);
throw error;
}
}

/**
* Adds a service to a setup in the multi-setup configuration.
*
* @param {string} service - The ID of the service to add.
* @param {string} [setupID=null] - The ID of the setup to add the service to.
*/
async addServiceIntoSetup(service, setupID) {
try {
let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);

// If the setup exists, add the service into it
if (setupsObj[setupID]) {
let setupServices = setupsObj[setupID].services;
if (!setupServices.includes(service.id)) {
setupServices.push(service.id);
}
await this.writeMultiSetup(setupsObj);
} else {
console.log("Setup not found!");
}
} catch (error) {
console.error(`Failed to add service to setup: ${error}`);
}
}

/**
* Deletes a service from a setup.
*
* @param {string} serviceID - The ID of the service to be deleted.
* @param {string} setupID - The ID of the setup from which the service will be deleted.
* @throws Will throw an error if the reading or writing operations fail.
*/
async deleteServiceFromSetup(serviceID, setupID) {
try {
let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);

// Check if the setup exists and contains the service
if (setupsObj[setupID] && setupsObj[setupID].services.includes(serviceID)) {
setupsObj[setupID].services = setupsObj[setupID].services.filter((id) => id !== serviceID);

await this.writeMultiSetup(setupsObj);
} else {
console.log(`Service ${serviceID} not found in setup ${setupsObj[setupID].name}`);
}
} catch (error) {
console.error(`Failed to delete service from multiSetup: ${error}`);
}
}

/**
* Deletes a setup.
*
* @param {string} setupID - The ID of the setup to be deleted.
* @throws Will throw an error if the reading or writing operations fail.
*/
async deleteSetup(setupID) {
try {
let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);

delete setupsObj[setupID];

await this.writeMultiSetup(setupsObj);
} catch (error) {
console.error(`Failed to delete service from multi-setup: ${error}`);
}
}

/**
* retrieves a specific setup by its ID
*
* @param {string} setupID - The ID of the setup to retrieve.
* @returns {Promise<Object>} A promise that resolves to the setup configuration.
*/
async getSetup(setupID) {
let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);

return {
[setupID]: setupsObj[setupID],
};
}

/**
* Creates a new setup and adds it to the multi-setup configuration.
*
* @param {Object} newSetup - The setup object to add.
*/
async createSetup(newSetup) {
let currentSetups = await this.readMultiSetup();
let setupsObj = {};
if (currentSetups) {
setupsObj = yaml.load(currentSetups);
}
let setupId = uuid.v4();
if (!setupsObj[setupId]) {
setupsObj[setupId] = newSetup;
await this.writeMultiSetup(setupsObj);
}
}

async getSetups() {
let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);

return setupsObj;
}

async switchSetupNetwork(data) {
if (typeof data.setupId !== "string" || typeof data.network !== "string") {
console.error("Invalid parameters");
return false;
}

let currentSetups = await this.readMultiSetup();
let setupsObj = yaml.load(currentSetups);
if (setupsObj.hasOwnProperty(data.setupId)) {
setupsObj[data.setupId].network = data.network;
await this.writeMultiSetup(setupsObj);
return true;
} else {
throw new Error("Setup ID not found"); // Enhanced error handling
}
}
}
6 changes: 5 additions & 1 deletion launcher/src/backend/NodeConnection.js
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ import { ServiceVolume } from "./ethereum-services/ServiceVolume";
import net from "net";
import YAML from "yaml";
import { NodeUpdates } from "./NodeUpdates";
import { ConfigManager } from "./ConfigManager";
import axios from "axios";
const log = require("electron-log");
const electron = require("electron");
@@ -29,6 +30,7 @@ export class NodeConnection {
this.os = null;
this.osv = null;
this.nodeUpdates = new NodeUpdates(this);
this.configManager = new ConfigManager(this);
}

async establish(taskManager, currentWindow) {
@@ -1811,8 +1813,9 @@ export class NodeConnection {
* write a specific service configuration
*
* @param serviceConfiguration servicd configuration to write to the node
* @param {string|null} setupID - The setup ID. Defaults to null.
*/
async writeServiceConfiguration(serviceConfiguration) {
async writeServiceConfiguration(serviceConfiguration, setupID = null) {
let configStatus;
const ref = StringUtils.createRandomString();
this.taskManager.tasks.push({ name: "write config", otherRunRef: ref });
@@ -1824,6 +1827,7 @@ export class NodeConnection {
serviceConfiguration.id +
".yaml"
);
if (setupID) await this.configManager.addServiceIntoSetup(serviceConfiguration, setupID);
} catch (err) {
this.taskManager.otherSubTasks.push({
name: "write " + serviceConfiguration.service + " config",
4 changes: 4 additions & 0 deletions launcher/src/backend/OneClickInstall.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ServiceManager } from "./ServiceManager";
import YAML from "yaml";
import { StringUtils } from "./StringUtils";
import { ConfigManager } from "./ConfigManager";

const log = require("electron-log");

@@ -13,6 +14,7 @@ export class OneClickInstall {
this.installDir = installDir;
this.nodeConnection = nodeConnection;
this.serviceManager = new ServiceManager(this.nodeConnection);
this.configManager = new ConfigManager(this.nodeConnection);
const arch = await this.nodeConnection.getCPUArchitecture();
const settings = {
stereum_settings: {
@@ -31,6 +33,7 @@ export class OneClickInstall {
await this.nodeConnection.sshService.exec(`rm -rf /etc/stereum &&\
mkdir -p /etc/stereum/services &&\
echo -e ${StringUtils.escapeStringForShell(YAML.stringify(settings))} > /etc/stereum/stereum.yaml`);
await this.configManager.createMultiSetupYaml({}, "");
await this.nodeConnection.findStereumSettings();
return await this.nodeConnection.prepareStereumNode(
this.nodeConnection.settings.stereum.settings.controls_install_path
@@ -399,6 +402,7 @@ export class OneClickInstall {
async writeConfig() {
const configs = this.getConfigurations();
if (configs[0] !== undefined) {
this.configManager.createMultiSetupYaml(configs, this.network);
await Promise.all(
configs.map(async (config) => {
await this.nodeConnection.writeServiceConfiguration(config);
42 changes: 35 additions & 7 deletions launcher/src/backend/SSHService.js
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import { StringUtils } from "./StringUtils";
import * as fs from "fs";
import * as path from "path";
const log = require("electron-log");
const ping = require("ping");

export class SSHService {
constructor() {
@@ -62,6 +63,27 @@ export class SSHService {
});
}

// Check the connection quality by pinging the host
async checkConnectionQuality() {
const host = this.connectionInfo.host;
let connectionQuality = { pingTime: null };

try {
const res = await ping.promise.probe(host, {
timeout: 2,
});

if (typeof res.time !== "undefined" && res.time !== null) {
connectionQuality.pingTime = res.time;
} else {
console.log(`Ping to ${host} failed or timed out`);
}
} catch (err) {
console.error("Ping failed:", err);
}
return connectionQuality;
}

async checkConnectionPool() {
let lastIndex = this.connectionPool.length - 1;
const threshholdIndex = lastIndex - 2;
@@ -118,8 +140,7 @@ export class SSHService {
if (!connectionInfo.authCode) {
currentWindow.send("require2FA", true);
conn.end();
}
else {
} else {
finish([connectionInfo.authCode.toString()]);
}
});
@@ -174,9 +195,14 @@ export class SSHService {
await new Promise((resolve) => setTimeout(resolve, 1000));
counter++;
}
log.info("SSH Channels left open: ", this.connectionPool.map(c => c._chanMgr?._count).reduce((accumulator, currentValue) => {
return accumulator + currentValue
}, 0))
log.info(
"SSH Channels left open: ",
this.connectionPool
.map((c) => c._chanMgr?._count)
.reduce((accumulator, currentValue) => {
return accumulator + currentValue;
}, 0)
);
this.connectionPool = [];
return true;
} catch (error) {
@@ -185,7 +211,8 @@ export class SSHService {
}

async exec(command, useSudo = true, useRoot = true) {
const ensureSudoCommand = `sudo -u ${useRoot ? 'root' : this.connectionInfo.user} -i <<'=====EOF'\n` + command + `\n=====EOF`;
const ensureSudoCommand =
`sudo -u ${useRoot ? "root" : this.connectionInfo.user} -i <<'=====EOF'\n` + command + `\n=====EOF`;
return this.execCommand(useSudo ? ensureSudoCommand : command);
}

@@ -373,7 +400,8 @@ export class SSHService {
if (sshDirPath.endsWith("/")) sshDirPath = sshDirPath.slice(0, -1, ""); //if path ends with '/' remove it
let newKeys = keys.join("\n");
let result = await this.exec(
`echo -e ${StringUtils.escapeStringForShell(newKeys)} > ${sshDirPath}/authorized_keys`, false
`echo -e ${StringUtils.escapeStringForShell(newKeys)} > ${sshDirPath}/authorized_keys`,
false
);
if (SSHService.checkExecError(result)) {
throw new Error("Failed writing authorized keys:\n" + SSHService.extractExecError(result));
370 changes: 290 additions & 80 deletions launcher/src/backend/ServiceManager.js

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions launcher/src/background.js
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ import { ValidatorAccountManager } from "./backend/ValidatorAccountManager.js";
import { TaskManager } from "./backend/TaskManager.js";
import { Monitoring } from "./backend/Monitoring.js";
import { StereumUpdater } from "./StereumUpdater.js";
import { ConfigManager } from "./backend/ConfigManager.js";
import { AuthenticationService } from "./backend/AuthenticationService.js";
import { SSHService } from "./backend/SSHService.js";
import path from "path";
@@ -23,6 +24,8 @@ const monitoring = new Monitoring(nodeConnection);
const oneClickInstall = new OneClickInstall();
const serviceManager = new ServiceManager(nodeConnection);
const validatorAccountManager = new ValidatorAccountManager(nodeConnection, serviceManager);
const configManager = new ConfigManager(nodeConnection);
configManager.setServiceManager(serviceManager);
const authenticationService = new AuthenticationService(nodeConnection);
const sshService = new SSHService();
const { globalShortcut } = require("electron");
@@ -637,13 +640,50 @@ ipcMain.handle("copyExecutionJWT", async (event, args) => {
return await serviceManager.copyExecutionJWT(args);
});

ipcMain.handle("readMultiSetup", async () => {
return await configManager.readMultiSetup();
});

ipcMain.handle("createSetup", async (event, args) => {
return await configManager.createSetup(args);
});

ipcMain.handle("deleteSetup", async (event, args) => {
return await configManager.deleteSetup(args);
});

ipcMain.handle("renameSetup", async (event, args) => {
return await configManager.renameSetup(args);
});

ipcMain.handle("exportSingleSetup", async (event, args) => {
return await serviceManager.exportSingleSetup(args);
});

ipcMain.handle("importSingleSetup", async (event, args) => {
return await serviceManager.importSingleSetup(args);
});

ipcMain.handle("switchSetupNetwork", async (event, args) => {
return await configManager.switchSetupNetwork(args);
});

ipcMain.handle("fetchTranslators", async (event, args) => {
return await serviceManager.fetchTranslators(args);
});

ipcMain.handle("fetchGitHubTesters", async (event, args) => {
return await serviceManager.fetchGitHubTesters(args);
});

ipcMain.handle("checkAndCreateMultiSetup", async () => {
return await configManager.checkAndCreateMultiSetup();
});

ipcMain.handle("checkConnectionQuality", async (event, args) => {
return await nodeConnection.sshService.checkConnectionQuality(args);
});

ipcMain.handle("startShell", async (event) => {
if (!nodeConnection.sshService.shellStream) {
try {
536 changes: 440 additions & 96 deletions launcher/src/components/UI/edit-page/EditScreen.vue

Large diffs are not rendered by default.

This file was deleted.

108 changes: 4 additions & 104 deletions launcher/src/components/UI/edit-page/components/drawer/DrawerBox.vue
Original file line number Diff line number Diff line change
@@ -1,111 +1,11 @@
import { useServices } from '@/store/services';
<template>
<div class="w-28 h-full flex flex-col justify-between items-center fixed right-0 top-0">
<div
class="w-44 h-full flex flex-col justify-between items-center fixed right-0 top-0 z-10"
>
<div
class="grid grid-cols-1 grid-rows-15 w-full h-full px-2 py-4 overflow-hidden border-l border-gray-500 rounded-l-xl bg-[#2e5151]"
>
<DrawerFilter />
<div
class="h-7 col-start-1 col-span-full row-start-3 row-span-1 mb-1 self-end flex justify-start items-center relative"
>
<label for="Search" class="sr-only"> {{ $t("multiServer.serch") }} </label>

<input
id="Search"
v-model="searchQuery"
type="text"
:placeholder="`${$t('multiServer.serchFor')}`"
class="w-full h-full rounded-md border-gray-200 py-2.5 pe-10 shadow-sm sm:text-sm px-2"
/>

<span class="absolute inset-y-0 end-0 grid w-10 place-content-center">
<button type="button" class="text-gray-600 hover:text-gray-700">
<span class="sr-only">{{ $t("multiServer.serch") }} </span>

<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
/>
</svg>
</button>
</span>
</div>

<div
class="col-span-1 row-start-4 row-span-full w-full h-[430px] flex flex-col items-center space-y-6 bg-[#151618] border border-gray-500 rounded-md overflow-y-scroll py-3 overflow-x-hidden"
>
<div
v-for="service in filteredServers"
:key="service.serviceID"
class="w-full relative inline-block cursor-pointer"
draggable="true"
@dragstart="dragStart($event, service)"
@dblclick="addServices(service)"
@mouseenter="footerStore.cursorLocation = `${service.name} ${serv}`"
>
<img :src="service.sIcon" alt="Client Icon" class="w-14 mx-auto" />
<p
v-if="service.displayTooltip"
class="min-w-[50px] h-[20px] absolute flex items-center justify-center p-1 text-gray-800 bg-white rounded-sm border border-gray-950 shadow-lg z-20"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-4 h-4 absolute rotate-45 right-4 -top-1 transform text-white fill-current"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path d="M20 3H4a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1z"></path>
</svg>
<span class="text-xs truncate z-10">{{ service.name }}</span>
</p>
</div>
</div>
<slot></slot>
</div>
</div>
</template>

<script setup>
import DrawerFilter from "./DrawerFilter.vue";
import { useServices } from "@/store/services";
import { useFooter } from "@/store/theFooter";
import i18n from "@/includes/i18n";
import { computed, ref } from "vue";
const t = i18n.global.t;
const serv = t("serviceLay.srvice");
const searchQuery = ref("");
const footerStore = useFooter();
const serviceStore = useServices();
const props = defineProps({
dragging: Function,
});
// eslint-disable-next-line vue/no-setup-props-destructure
const dragStart = props.dragging;
const emit = defineEmits(["addServices", "startDrag"]);
const filteredServers = computed(() => {
if (!searchQuery.value) {
return serviceStore.filteredServices;
}
return serviceStore.filteredServices.filter((service) =>
service.name.toLowerCase().includes(searchQuery.value.toLowerCase())
);
});
function addServices(service) {
emit("addServices", service);
}
</script>
159 changes: 102 additions & 57 deletions launcher/src/components/UI/edit-page/components/drawer/DrawerFilter.vue
Original file line number Diff line number Diff line change
@@ -1,52 +1,58 @@
<template>
<div class="col-span-1 row-start-1 row-end-2 z-10 flex">
<Listbox v-model="selectedfilter">
<div class="w-full relative mt-1">
<ListboxButton class="relative w-full cursor-default rounded-md bg-gray-200 p-1 text-left shadow-md text-sm">
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-3 w-3 text-gray-400" aria-hidden="true" />
</span>
<span class="block truncate">{{ selectedfilter.name }}</span>
</ListboxButton>

<transition
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
<div class="col-start-1 col-span-full row-start-2 row-span-1 z-10 flex">
<div class="w-full relative mt-1">
<button
class="relative w-full cursor-default rounded-md bg-gray-200 p-1 text-left shadow-md text-sm"
@click="toggleDropdown"
>
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
>
<ChevronUpDownIcon class="h-3 w-3 text-gray-400" aria-hidden="true" />
</span>
<span class="block truncate text-center">{{ selectedFilter.name }}</span>
</button>
<transition
name="fade"
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ul
v-if="isOpen"
class="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none text-sm"
>
<ListboxOptions
class="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none text-sm"
<li
v-for="filter in filters"
:key="filter.name"
class="flex justify-center cursor-default select-none p-1 text-gray-900 hover:bg-gray-300"
@click="selectFilter(filter)"
>
<ListboxOption
v-for="filter in filters"
v-slot="{ active, selected }"
:key="filter.name"
:value="filter"
as="template"
<span
v-if="selectedFilter.name === filter.name"
class="flex items-center text-cyan-600"
>
<CheckIcon class="h-3 w-3" aria-hidden="true" />
</span>
<span
class="block truncate"
:class="{
'font-medium': selectedFilter.name === filter.name,
'font-normal': selectedFilter.name !== filter.name,
}"
>
<li
:class="[
active ? 'bg-gray-300 text-cyan-500' : 'text-gray-900',
'flex justify-center cursor-default select-none p-1',
]"
>
<span v-if="selected" class="flex items-center text-cyan-600">
<CheckIcon class="h-3 w-3" aria-hidden="true" />
</span>
<span :class="[selected ? 'font-medium' : 'font-normal', 'block truncate']">{{ filter.name }}</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
{{ filter.name }}
</span>
</li>
</ul>
</transition>
</div>
</div>
</template>

<script setup>
import { computed, ref } from "vue";
import { Listbox, ListboxButton, ListboxOptions, ListboxOption } from "@headlessui/vue";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { computed, onMounted, ref, watch } from "vue";
import { ChevronUpDownIcon, CheckIcon } from "@heroicons/vue/20/solid";
import { useServices } from "@/store/services";
import { useNodeManage } from "@/store/nodeManage";
@@ -60,37 +66,76 @@ const filters = [
const serviceStore = useServices();
const manageStore = useNodeManage();
const selectedFilter = ref(filters[0]);
const isOpen = ref(false);
const selectedfilter = ref(filters[0]);
const toggleDropdown = () => {
isOpen.value = !isOpen.value;
};
serviceStore.filteredServices = computed(() => {
if (selectedfilter.value.name.toLowerCase() === "all") {
return serviceStore.allServices.filter(getFilterbyNetwork());
} else {
return serviceStore.allServices
.filter((item) => item.category.toLowerCase() === selectedfilter.value.name.toLowerCase())
.filter(getFilterbyNetwork());
}
const selectFilter = (filter) => {
selectedFilter.value = filter;
isOpen.value = false;
};
const networkArchFilteredServices = computed(() => {
return serviceStore.allServices.filter((service) => {
return networkFilter(service) && archFilter(service);
});
});
const getFilterbyNetwork = () => {
const filteredServices = () => {
const filterName = selectedFilter.value?.name?.toLowerCase() ?? "all";
return filterName === "all"
? networkArchFilteredServices.value
: networkArchFilteredServices.value.filter(
(service) => service.category.toLowerCase() === filterName.toLowerCase()
);
};
onMounted(() => {
serviceStore.filteredServices = filteredServices();
});
watch(selectedFilter, () => {
serviceStore.filteredServices = filteredServices();
});
//serviceStore.filteredServices
//Methods
const networkFilter = (service) => {
switch (manageStore.configNetwork.network) {
case "mainnet":
return (item) => archFilter(item.service);
case "holesky":
return (item) => item.service != archFilter(item.service);
return service.service !== "SSVNetworkService";
case "sepolia":
return (item) => item.service != "SSVNetworkService" && archFilter(item.service);
return service.service !== "SSVNetworkService";
case "gnosis":
return (item) =>
/(Lighthouse|Teku|Nethermind|Erigon|Grafana|Prometheus)/.test(item.service) && archFilter(item.service);
return /(Lighthouse|Teku|Nethermind|Erigon|Grafana|Prometheus)/.test(
service.service
);
default:
return (item) => item.service != "SSVNetworkService" && archFilter(item.service);
return service.service !== "SSVNetworkService";
}
};
const archFilter = (service) => {
const armArchs = ["arm", "arm64", "aarch64_be", "aarch64", "armv8b", "armv8l"];
return armArchs.includes(manageStore.architecture)
? !/(Prysm|ValidatorEjector|KeysAPI|Notification)/.test(service)
? !/(Prysm|ValidatorEjector|KeysAPI|Notification)/.test(service.service)
: true;
};
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.1s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
109 changes: 109 additions & 0 deletions launcher/src/components/UI/edit-page/components/drawer/DrawerMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<template>
<div
class="w-full h-full col-start-1 col-span-full row-start-1 row-span-full grid grid-cols-6 grid-rows-15 items-center gap-y-2"
>
<div
class="col-start-1 col-span-full row-start-1 row-end-3 w-full h-full bg-[#232428] rounded-md flex flex-col justify-between items-center p-1"
>
<span class="text-xs text-center text-gray-100 font-sans uppercase mt-1"
>Create a setup</span
>

<div
class="w-full h-7 bg-teal-700 rounded-[5px] text-center cursor-pointer hover:bg-teal-900 hover:scale-95 active:scale-90 transition-all duration-100"
@click="createSetup"
>
<span class="h-full text-xs text-gray-200 uppercase">Create</span>
</div>
</div>
<div
class="col-start-1 col-span-full row-start-3 row-end-5 w-full h-full bg-[#232428] rounded-md flex flex-col justify-between items-center p-1"
>
<span class="text-xs text-center text-gray-100 font-sans uppercase mt-1"
>IMPORT A CONFIG</span
>

<div
class="w-full h-7 bg-teal-700 rounded-[5px] text-center cursor-pointer hover:bg-teal-900 hover:scale-95 active:scale-90 transition-all duration-100"
@click="importSetup"
>
<span class="text-xs h-full text-gray-200 uppercase">Import</span>
</div>
</div>
<div
class="col-start-1 col-span-full row-start-5 row-end-10 w-full h-full bg-[#232428] rounded-md grid grid-cols-6 grid-rows-6 items-start gap-y-1 p-1"
>
<span
class="col-start-1 col-span-full row-start-1 row-span-1 text-xs text-center text-gray-100 font-sans uppercase"
>ADD A SERVER SERVICE</span
>
<div
class="w-full h-full col-start-1 col-span-full row-start-2 row-span-full max-h-full overflow-x-hidden overflow-y-auto flex flex-col justify-start items-center space-y-1 bg-[#151618] p-1"
>
<div
v-for="service in getServerServices"
:key="service"
class="w-full h-7 min-h-7 bg-[#282a2c] hover:bg-gray-700 rounded-sm border border-gray-600 mx-auto shadow-md shadow-black grid grid-cols-6 items-center p-[2px] cursor-pointer overflow-hidden"
:class="service.isDuplicated ? 'pointer-events-none opacity-50' : ''"
@dblclick="addService(service)"
>
<img
class="w-5 h-5 col-start-1 col-span-1 mx-auto self-center"
:src="service.icon"
alt="Service Icon"
/>
<span
class="col-start-2 col-span-full self-center text-xs text-gray-200 text-left font-sans truncate"
>{{ service.service }}</span
>
</div>
</div>
</div>
</div>
</template>

<script setup>
import { computed, onMounted, ref } from "vue";
import { useServices } from "@/store/services";
import { useSetups } from "../../../../../store/setups";
import { useNodeManage } from "../../../../../store/nodeManage";
const emit = defineEmits(["importSetup", "createSetup", "addService"]);
const serviceStore = useServices();
const setupStore = useSetups();
const manageStore = useNodeManage();
const allServices = ref([]);
const getServerServices = computed(() => {
const newConfigServices = new Set(manageStore.newConfiguration.map((e) => e.service));
const serverServices = new Set(setupStore.serverServices.map((e) => e));
return allServices.value
.filter((e) => e.category === "service" && serverServices.has(e.service))
.map((service) => ({
...service,
isDuplicated: newConfigServices.has(service.service),
}))
.sort((a, b) => a.name.localeCompare(b.name));
});
onMounted(() => {
allServices.value = serviceStore.allServices
.filter((e) => e.category === "service")
.map((e) => ({ ...e, isDuplicated: false }));
});
// Methods
const importSetup = () => {
emit("importSetup");
};
const createSetup = () => {
emit("createSetup");
};
const addService = (service) => {
emit("addService", service);
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<template>
<div
class="w-full h-full col-start-1 col-span-full row-start-1 row-span-full grid grid-cols-6 grid-rows-15 items-center gap-y-1 bg-[#292e32] p-1 rounded-md"
>
<span
class="col-start-1 col-span-full row-start-1 row-span-1 text-xs text-center text-gray-100 font-sans uppercase"
>ADD A CONFIG SERVICE</span
>
<DrawerFilter />
<div
class="h-7 col-start-1 col-span-full row-start-3 row-span-1 mb-1 self-end flex justify-start items-center relative"
>
<label for="Search" class="sr-only"> {{ $t("multiServer.serch") }} </label>

<input
id="Search"
v-model="searchQuery"
type="text"
:placeholder="`${$t('multiServer.serchFor')}`"
class="w-full h-full rounded-md border-gray-200 py-2.5 pe-10 shadow-sm sm:text-sm px-2"
/>

<span class="absolute inset-y-0 end-0 grid w-10 place-content-center">
<button type="button" class="text-gray-600 hover:text-gray-700">
<span class="sr-only">{{ $t("multiServer.serch") }} </span>

<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-4 w-4"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
/>
</svg>
</button>
</span>
</div>

<div
class="col-start-1 col-span-full row-start-4 row-end-14 w-full h-full flex flex-col justify-start items-center space-y-1 bg-[#151618] border border-gray-700 rounded-md overflow-y-scroll py-1 overflow-x-hidden"
>
<div
v-for="service in filteredServices"
:key="service.service"
class="w-full h-10 border border-gray-800 rounded-sm cursor-pointer grid grid-cols-5 p-1 hover:bg-gray-700 transition-all duration-100"
draggable="true"
@dragstart="dragStart($event, service)"
@click="addServices(service)"
@mouseenter="footerStore.cursorLocation = `${service.name} ${serv}`"
>
<img
:src="service.icon"
alt="Client Icon"
class="col-start-1 col-span-1 w-full mx-auto"
/>
<span
class="col-start-2 col-span-full w-full self-center text-xs text-gray-100 truncate ml-2"
>{{ service.name }}</span
>
</div>
</div>
<div
class="col-start-1 col-span-full row-start-14 row-span-full w-full h-full bg-[#151618] rounded-md flex flex-col justify-between items-center p-1 shadow-sm shadow-black active:shadow-none border border-gray-700"
>
<span class="text-xs text-center text-gray-100 font-sans uppercase mt-1"
>CUSTOM SERVICE</span
>

<div
class="w-full h-8 bg-teal-700 rounded-sm text-center p-1 cursor-pointer hover:bg-teal-900 transition-all duration-100"
@click="addServices(customService)"
>
<span class="text-sm text-gray-200"> Create </span>
</div>
</div>
</div>
</template>
<script setup>
import DrawerFilter from "./DrawerFilter.vue";
import { useServices } from "@/store/services";
import { useFooter } from "@/store/theFooter";
import i18n from "@/includes/i18n";
import { computed, onUnmounted, ref } from "vue";
import { useSetups } from "../../../../../store/setups";
const t = i18n.global.t;
const serv = t("serviceLay.srvice");
const searchQuery = ref("");
const footerStore = useFooter();
const serviceStore = useServices();
const setupStore = useSetups();
const props = defineProps({
dragging: Function,
});
// eslint-disable-next-line vue/no-setup-props-destructure
const dragStart = props.dragging;
const emit = defineEmits(["addServices", "startDrag"]);
const filteredServices = computed(() => {
return serviceStore.filteredServices
.filter((service) => {
return !setupStore.serverServices.includes(service.service);
})
.filter((service) => {
return service.service !== "CustomService";
})
.filter((service) => {
return service.name.toLowerCase().includes(searchQuery.value.toLowerCase());
});
});
const customService = computed(() => {
return serviceStore.allServices.find((service) => service.service === "CustomService");
});
const addServices = (service) => {
emit("addServices", service);
};
onUnmounted(() => {
searchQuery.value = "";
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<template>
<div
class="w-full h-full col-start-1 col-span-full row-start-1 row-span-full grid grid-cols-6 grid-rows-15 items-center gap-y-2"
>
<div
class="col-start-1 col-span-full row-start-1 row-span-1 w-full h-full bg-[#151618] rounded-md p-1 flex justify-center items-center"
>
<div class="w-full h-full rounded-sm flex justify-center items-center">
<span
class="text-sm text-center text-gray-100 font-normal font-sans uppercase mt-1"
>Select A Network</span
>
</div>
</div>
<div
class="col-start-1 col-span-full row-start-2 row-end-14 w-full h-full bg-[#1d1f21] rounded-md grid grid-cols-2 grid-rows-12"
>
<div
class="col-start-1 col-span-full row-start-1 row-span-full bg-black rounded-b-md flex flex-col justify-start items-center p-1 overflow-x-hidden overflow-y-auto space-y-1 border border-gray-700 scrollbar scrollbar-thumb-gray-500 scrollbar-track-gray-800"
>
<div
v-for="network in manageStore.networkList"
:key="network"
class="w-full h-7 bg-[#282a2c] rounded-sm border border-gray-500 items-center mx-auto cursor-pointer hover:bg-gray-700 transition-all duration-100 grid grid-cols-5"
@click="getNetwork(network)"
>
<img
class="col-start-1 col-span-1 w-5 self-center mx-auto"
:src="network.icon"
alt="Network"
/>
<span
class="col-start-2 col-span-full text-left text-xs font-sans text-gray-200 ml-1"
>{{ network?.name }}
</span>
</div>
</div>
</div>

<div
class="col-start-1 col-span-full row-start-14 row-span-full w-full h-full bg-[#151618] rounded-md flex flex-col justify-between items-center p-1 shadow-sm shadow-black active:shadow-none border border-gray-700"
>
<span
class="text-2xs text-center text-gray-100 font-semibold font-sans uppercase mt-1"
>Create Custom Setup</span
>

<div
class="w-full h-8 bg-teal-700 rounded-sm text-center p-1 cursor-pointer hover:bg-teal-900 transition-all duration-100"
@click="createCustom"
>
<span class="text-sm text-gray-200 uppercase">Custom Setup</span>
</div>
</div>
</div>
</template>

<script setup>
import { useNodeManage } from "../../../../../store/nodeManage";
const emit = defineEmits(["getNetwork", "createCustom"]);
const manageStore = useNodeManage();
const getNetwork = (network) => {
emit("getNetwork", network);
};
const createCustom = () => {
emit("createCustom");
};
</script>
168 changes: 168 additions & 0 deletions launcher/src/components/UI/edit-page/components/edit/ConfigBody.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<template>
<div
class="w-full h-full max-h-[430px] rounded-md border overflow-hidden bg-[#151618] relative"
:class="[
isOverDropZone ? 'border-dashed border-2 border-blue-500 ' : 'border-gray-600',
manageStore.disableConfirmButton ? 'opacity-70 pointer-events-none' : '',
setupStore.isEditConfigViewActive
? 'animate__animated animate__fadeIn animate__faster'
: 'animate__animated animate__fadeOut animate__faster',
]"
>
<div
class="absolute top-0 w-full mx-auto grid grid-cols-3 h-6 border border-gray-950 rounded-t-[5px] text-xs font-[400] font-sans"
:class="[
setupStore.getBGColor(setupStore.selectedSetup?.color),
setupStore.getTextColor(setupStore.selectedSetup?.color),
]"
>
<span class="col-start-1 justify-self-center self-center">{{
$t("editBody.executionClient")
}}</span>
<span class="col-start-2 justify-self-center self-center">{{
$t("editBody.consensusClient")
}}</span>
<span class="col-start-3 justify-self-center self-center">{{
$t("editBody.validator")
}}</span>
</div>
<div
ref="dropZoneRef"
class="w-full h-full max-h-[428px] grid grid-cols-3 pt-5 z-10"
:class="{
'scrollbar scrollbar-rounded-* scrollbar-thumb-teal-800 scrollbar-track-transparent overflow-y-auto': activateScrollBar,
}"
@drop="onDrop($event)"
@dragover.prevent="isOverDropZone = true"
@dragleave.prevent="isOverDropZone = false"
>
<span
v-if="isOverDropZone"
class="col-start-2 col-span-1 self-center justify-self-center flex justify-center items-center text-xl text-blue-400"
>+</span
>
<ExecutionClients
v-if="!isOverDropZone"
@delete-service="deleteService"
@switch-client="switchClient"
@confirm-consensus="confirmConsensus"
@info-modal="infoModal"
@mouse-over="lineDraw"
@mouse-leave="removeLines"
/>

<ConsensusClients
v-if="!isOverDropZone"
@delete-service="deleteService"
@confirm-connection="confirmConnection"
@switch-client="switchClient"
@modify-service="modifyService"
@info-modal="infoModal"
@mouse-over="lineDraw"
@mouse-leave="removeLines"
/>
<ValidatorClients
v-if="!isOverDropZone"
@delete-service="deleteService"
@switch-client="switchClient"
@modify-service="modifyService"
@info-modal="infoModal"
@mouse-over="lineDraw"
@mouse-leave="removeLines"
/>
</div>
</div>
</template>

<script setup>
import ConsensusClients from "./clients/ConsensusClients.vue";
import ExecutionClients from "./clients/ExecutionClients.vue";
import ValidatorClients from "./clients/ValidatorClients.vue";
import { useNodeManage } from "@/store/nodeManage";
import { computed, onMounted, ref, watch } from "vue";
import { useSetups } from "../../../../../store/setups";
import { useMultiSetups } from "../../../../../composables/multiSetups";
const emit = defineEmits([
"onDrop",
"confirmConnection",
"switchClient",
"deleteService",
"confirmConsensus",
"infoModal",
"modifyService",
"removeLines",
"lineDraw",
]);
const manageStore = useNodeManage();
const setupStore = useSetups();
const { updateDom } = useMultiSetups();
const isOverDropZone = ref(false);
const activateScrollBar = computed(() => {
const validators = manageStore.newConfiguration.filter(
(service) => service.category === "validator"
);
const consensus = manageStore.newConfiguration.filter(
(service) => service.category === "consensus"
);
const execution = manageStore.newConfiguration.filter(
(service) => service.category === "execution"
);
return !!(validators.length > 3 || consensus.length > 3 || execution.length > 3);
});
//Update selected setup
watch(
() => manageStore.newConfiguration,
() => {
updateDom();
}
);
//Lifecycle Hooks
onMounted(() => {
updateDom();
});
// Methods
const removeLines = () => {
emit("removeLines");
};
const lineDraw = (service) => {
emit("lineDraw", service);
};
const onDrop = (event) => {
emit("onDrop", event);
};
const deleteService = (service) => {
emit("deleteService", service);
};
const switchClient = (service) => {
emit("switchClient", service);
};
const confirmConsensus = (service) => {
emit("confirmConsensus", service);
};
const confirmConnection = (service) => {
emit("confirmConnection", service);
};
const infoModal = (service) => {
emit("infoModal", service);
};
const modifyService = (service) => {
emit("modifyService", service);
};
</script>

This file was deleted.

372 changes: 180 additions & 192 deletions launcher/src/components/UI/edit-page/components/edit/EditBody.vue

Large diffs are not rendered by default.

58 changes: 38 additions & 20 deletions launcher/src/components/UI/edit-page/components/edit/EditHeader.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,51 @@
<template>
<div class="w-full h-[55px] grid grid-cols-9 gap-1 py-1">
<ServerDetails />

<ConfigDetails :list="configsToDisplay" />

<SetupDetails
:list="setupsList"
@select-rename="selectRename"
@confirm-rename="confirmRename"
@select-setup="selectSetup"
@server-view="serverView"
/>
<NetworkDetails />
</div>
</template>

<script setup>
import { useNodeManage } from "@/store/nodeManage";
import ServerDetails from "./ServerDetails.vue";
import NetworkDetails from "./NetworkDetails.vue";
import ConfigDetails from "./ConfigDetails.vue";
import { useMultiSetups } from "@/composables/multiSetups";
import { useSetups } from "@/store/setups";
import { computed } from "vue";
import { useRoute } from "vue-router";
const nodeStore = useNodeManage();
const route = useRoute();
const configsToDisplay = computed(() => {
let configs;
if (route.path === "/node") {
configs = nodeStore.nodeConfigs;
} else {
configs = nodeStore.nodeConfigs.slice(0, 4);
}
return configs;
import NetworkDetails from "./header/NetworkDetails.vue";
import ServerDetails from "./header/ServerDetails.vue";
import SetupDetails from "./header/SetupDetails.vue";
const emit = defineEmits(["selectRename", "confirmRename"]);
const setupStore = useSetups();
const { getSelectedSetup, getServerView } = useMultiSetups();
const setupsList = computed(() => {
return setupStore.editSetups;
});
const selectRename = (setup) => {
emit("selectRename", setup);
};
const confirmRename = () => {
emit("confirmRename");
};
const selectSetup = (setup) => {
getSelectedSetup(setup, true);
};
const serverView = () => {
getServerView();
};
</script>

<style scoped>
.fade-move,
.fade-enter-active,

This file was deleted.

64 changes: 64 additions & 0 deletions launcher/src/components/UI/edit-page/components/edit/SetupBody.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<template>
<div
class="w-full h-full max-h-[430px] rounded-md border border-gray-600 overflow-hidden mt-1 bg-[#151618] relative"
:class="[
manageStore.disableConfirmButton ? 'opacity-70 pointer-events-none' : '',
setupStore.isEditConfigViewActive
? 'animate__animated animate__fadeOut animate__faster'
: 'animate__animated animate__fadeIn animate__faster',
]"
>
<div class="w-full h-full max-h-full rounded-md grid grid-cols-24 grid-rows-12">
<div
class="col-start-1 col-span-full row-start-1 row-span-1 w-full mx-auto h-6 bg-[#33393E] border border-gray-950 rounded-t-[5px] flex justify-center items-center overflow-hidden"
>
<span class="text-xs text-gray-300 text-center font-sans">All Setups</span>
</div>
<div
class="w-full h-full col-start-1 col-span-full row-start-2 row-span-full grid grid-cols-3 grid-rows-3 scrollbar scrollbar-rounded-* scrollbar-thumb-teal-800 scrollbar-track-transparent overflow-y-auto overflow-x-hidden items-start"
>
<SingleSetup
v-for="setup in getEditSetups.filter((s) => s.setupName !== 'commonServices')"
:key="setup.setupName"
:setup="setup"
@delete-setup="deleteSetup"
@connect-setup="connectSetup"
@info-modal="infoModal"
@open-configs="openConfigs"
/>
</div>
</div>
</div>
</template>

<script setup>
import { useNodeManage } from "@/store/nodeManage";
import { computed } from "vue";
import { useSetups } from "../../../../../store/setups";
import SingleSetup from "./setups/SingleSetup.vue";
const emit = defineEmits(["deleteSetup", "connectSetup", "setupInfos", "openConfigs"]);
const manageStore = useNodeManage();
const setupStore = useSetups();
const getEditSetups = computed(() => {
return setupStore.editSetups;
});
const deleteSetup = (item) => {
emit("deleteSetup", item);
};
const connectSetup = (item) => {
emit("connectSetup", item);
};
const infoModal = (item) => {
emit("setupInfos", item);
};
const openConfigs = (item) => {
emit("openConfigs", item);
};
</script>
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<template>
<div class="relative w-full h-[60px] col-start-5 col-end-13 flex justify-center items-center gap-x-2">
<div
class="relative w-full h-[60px] col-start-5 col-end-13 flex justify-center items-center gap-x-2"
>
<Carousel
ref="carousel"
v-model="currentSlide"
@@ -14,10 +16,17 @@
class="w-11/12 h-full bg-[#33393e] flex justify-center items-center border border-gray-600 rounded-lg"
:style="{ 'pointer-events': getCategory === 'execution' ? 'none' : '' }"
>
<div v-if="item.name === 'genesis'" class="w-full h-full flex justify-evenly items-center p-1">
<div class="w-full h-full flex flex-col justify-evenly items-center text-gray-400 p-1">
<div
v-if="item.name === 'genesis'"
class="w-full h-full flex justify-evenly items-center p-1"
>
<div
class="w-full h-full flex flex-col justify-evenly items-center text-gray-400 p-1"
>
<span class="w-full font-semibold text-md uppercase">{{ item.name }}</span>
<span class="w-full font-semibold text-md uppercase text-teal-600">{{ item.type }}</span>
<span class="w-full font-semibold text-md uppercase text-teal-600">{{
item.type
}}</span>
</div>
</div>
<div
@@ -26,7 +35,9 @@
>
<div class="w-1/3 h-full flex flex-col justify-evenly items-center">
<span class="text-sm text-gray-400 capitalize">{{ item.name }}</span>
<span class="text-xs font-semibold uppercase text-teal-600">{{ item.type }}</span>
<span class="text-xs font-semibold uppercase text-teal-600">{{
item.type
}}</span>
</div>
<div class="w-2/3 h-full cursor-pointer">
<input
@@ -43,7 +54,9 @@
>
<div class="w-1/3 h-full flex flex-col justify-evenly items-center">
<span class="text-sm text-gray-400 capitalize">{{ item.name }}</span>
<span class="text-xs font-semibold uppercase text-teal-600">{{ item.type }}</span>
<span class="text-xs font-semibold uppercase text-teal-600">{{
item.type
}}</span>
</div>

<div class="w-2/3 h-full cursor-pointer p-1">
@@ -61,14 +74,22 @@
<div v-if="selectedIcon !== ''" class="w-1/6" @click="openDropdown">
<img class="w-5 h-6 ml-2" :src="selectedIcon" :alt="selectedItem" />
</div>
<div v-if="selectedIcon !== ''" class="w-4/6 text-md text-gray-300 font-semibold" @click="openDropdown">
<div
v-if="selectedIcon !== ''"
class="w-4/6 text-md text-gray-300 font-semibold"
@click="openDropdown"
>
{{ selectedItem }}
</div>
<div v-else class="w-4/6 text-gray-500 text-sm" @click="openDropdown">
{{ selectedItem }}
</div>
<div class="w-1/6" @click="openWindow">
<img class="w-6" src="/img/icon/service-modals-icons/internet.png" alt="Internet" />
<img
class="w-6"
src="/img/icon/service-modals-icons/internet.png"
alt="Internet"
/>
</div>
</div>
</div>
@@ -143,7 +164,10 @@ const getCategory = computed(() => {
// Watchers
watch(currentSlide, (val) => {
if (router.currentRoute.value.path === "/sync" || router.currentRoute.value.path === "/importingSyncing") {
if (
router.currentRoute.value.path === "/sync" ||
router.currentRoute.value.path === "/importingSyncing"
) {
if (val !== prevVal.value) {
prevVal.value = val;
installStore.checkPointSync = "";
@@ -167,7 +191,7 @@ onBeforeMount(() => {
});
onMounted(() => {
manageStore.currentNetwork = manageStore.currentNetwork.hasOwnProperty("id")
manageStore.currentNetwork = manageStore.currentNetwork?.hasOwnProperty("id")
? manageStore.currentNetwork
: manageStore.configNetwork;
setSelectedLinks();
Original file line number Diff line number Diff line change
@@ -37,6 +37,7 @@ import ClientLayout from "./ClientLayout.vue";
import GeneralMenu from "./GeneralMenu.vue";
import { computed } from "vue";
import { useSetups } from "../../../../../../store/setups";
//Props & Emits
const emit = defineEmits([
@@ -51,23 +52,22 @@ const emit = defineEmits([
//Refs
const manageStore = useNodeManage();
const setupStore = useSetups();
// computed & watchers properties
const getConsensus = computed(() => {
return manageStore.newConfiguration
.filter((e) => e.category == "consensus")
const services = manageStore.newConfiguration
.filter(
(s) => s.setupId === setupStore.selectedSetup?.setupId && s.category === "consensus"
)
.sort((a, b) => {
let fa = a.name.toLowerCase(),
fb = b.name.toLowerCase();
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
const fa = a.name.toLowerCase();
const fb = b.name.toLowerCase();
return fa < fb ? -1 : fa > fb ? 1 : 0;
});
return services;
});
// methods
Original file line number Diff line number Diff line change
@@ -36,26 +36,24 @@ import ClientLayout from "./ClientLayout.vue";
import GeneralMenu from "./GeneralMenu.vue";
import { computed } from "vue";
import { useSetups } from "../../../../../../store/setups";
const emit = defineEmits(["deleteService", "switchClient", "connectClient", "infoModal", "mouseOver", "mouseLeave"]);
const manageStore = useNodeManage();
const serviceStore = useServices();
const setupStore = useSetups();
const getExecutions = computed(() => {
return manageStore.newConfiguration
.filter((e) => e.category == "execution")
const services = manageStore.newConfiguration
.filter((s) => s.category === "execution" && s.setupId === setupStore.selectedSetup.setupId)
.sort((a, b) => {
let fa = a.name.toLowerCase(),
fb = b.name.toLowerCase();
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
const fa = a.name.toLowerCase();
const fb = b.name.toLowerCase();
return fa < fb ? -1 : fa > fb ? 1 : 0;
});
return services;
});
// Methods
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
>
<div
v-for="item in getValidators"
:key="item"
:key="item.name"
:ref="
(el) => {
item.ref = el;
@@ -34,50 +34,52 @@

<script setup>
import { useNodeManage } from "@/store/nodeManage";
import { computed } from "vue";
import { useSetups } from "@/store/setups";
import ClientLayout from "./ClientLayout.vue";
import GeneralMenu from "./GeneralMenu.vue";
import { computed } from "vue";
// Variables & Constants
const emit = defineEmits(["deleteService", "switchClient", "modifyService", "infoModal", "mouseOver", "mouseLeave"]);
const emit = defineEmits([
"deleteService",
"switchClient",
"modifyService",
"infoModal",
"mouseOver",
"mouseLeave",
]);
const manageStore = useNodeManage();
const setupStore = useSetups();
// Computed & Watchers
// Use computed for reactivity
const getValidators = computed(() => {
let service;
service = manageStore.newConfiguration
.filter((e) => e.category == "validator")
const services = manageStore.newConfiguration
.filter(
(s) => s.setupId === setupStore.selectedSetup?.setupId && s.category === "validator"
)
.sort((a, b) => {
let fa = a.name.toLowerCase(),
fb = b.name.toLowerCase();
if (fa < fb) {
return -1;
}
if (fa > fb) {
return 1;
}
return 0;
const fa = a.name.toLowerCase();
const fb = b.name.toLowerCase();
return fa < fb ? -1 : fa > fb ? 1 : 0;
});
return service;
return services;
});
// Methods
const getDynamicClasses = (item) => {
if (item.hasOwnProperty("isRemoveProcessing") && item.isRemoveProcessing) {
return "border bg-red-600 border-white hover:bg-red-600";
} else if (item.hasOwnProperty("isNewClient") && item.isNewClient) {
return "opacity-50 cursor-not-allowed pointer-events-none bg-[#212629] border border-gray-700";
return "opacity-50 cursor-not-allowed pointer-events-none bg-[#212629] border border-gray-700";
} else if (item.hasOwnProperty("modifierPanel") && item.modifierPanel) {
return "opacity-50 cursor-not-allowed pointer-events-none bg-[#212629] border border-gray-700";
return "opacity-50 cursor-not-allowed pointer-events-none bg-[#212629] border border-gray-700";
} else {
return "bg-[#212629] hover:bg-[#374045] border border-gray-700";
}
};
// Methods
const displayMenu = (item) => {
manageStore.newConfiguration.forEach((service) => {
service.displayPluginMenu = false;
@@ -123,6 +125,7 @@ const infoModal = (item) => {
emit("infoModal", item);
};
</script>
<style scoped>
.slide-fade-enter-active {
transition: all 0.3s ease-out;
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<template>
<div
class="col-start-7 col-span-3 flex flex-col justify-between items-center bg-[#151618] border h-full border-gray-600 rounded-md px-2 py-1"
style="cursor: default"
@mouseenter="footerStore.cursorLocation = `${currIs} ${network}`"
@mouseleave="footerStore.cursorLocation = ''"
>
<div
v-if="setupStore.isConfigViewActive || setupStore.isEditConfigViewActive"
class="w-full self-start text-xs font-semibold text-teal-700"
>
{{ t("networkDetails.currentNet") }}
</div>
<div
v-else
class="w-full self-start text-xs font-semibold text-teal-700 overflow-hidden"
>
TOTAL SETUPS ON SERVER
</div>
<div
v-if="setupStore.isConfigViewActive || setupStore.isEditConfigViewActive"
class="w-full flex justify-center items-center"
>
<img
v-if="getSetupNetwork"
:src="getSetupNetwork?.icon"
alt="Networks"
class="w-5 mr-1"
/>
<span class="text-md text-gray-300 text-left overflow-hidden whitespace-pre">{{
getSetupNetwork?.name
}}</span>
</div>
<div v-else class="w-full flex justify-center items-center">
<span class="text-md text-gray-300 text-left overflow-hidden whitespace-pre">{{
totalNetworks
}}</span>
</div>
</div>
</template>
<script setup>
import { useNodeManage } from "@/store/nodeManage";
import { computed } from "vue";
import { useFooter } from "@/store/theFooter";
import i18n from "@/includes/i18n";
import { useSetups } from "@/store/setups";
const t = i18n.global.t;
const currIs = t("networkDetails.currIs");
const footerStore = useFooter();
const manageStore = useNodeManage();
const setupStore = useSetups();
const totalNetworks = computed(() => {
return (
setupStore.allSetups
.filter((setup) => setup.setupName !== "commonServices")
.map((setup) => setup).length || 0
);
});
const getSetupNetwork = computed(() => {
let setupNet;
const net = setupStore.selectedSetup?.network;
if (net) {
setupNet = manageStore.networkList.find((network) => network.network === net);
}
return setupNet;
});
</script>
<style scoped>
.fade-move,
.fade-enter-active,
.fade-leave-active {
transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1);
}
/* 2. declare enter from and leave to state */
.fade-enter-from,
.fade-leave-to {
opacity: 0;
transform: scaleY(0.01) translate(30px, 0);
}
/* 3. ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.fade-leave-active {
position: absolute;
}
</style>
Original file line number Diff line number Diff line change
@@ -4,29 +4,36 @@
style="cursor: default"
>
<div
class="w-full flex justify-center items-center px-2 h-[25px]"
class="w-full px-2 h-[25px] grid grid-cols-24 items-center"
@mouseenter="footerStore.cursorLocation = `${machineName}`"
@mouseleave="footerStore.cursorLocation = ''"
>
<span class="text-md font-semibold ml-1 text-yellow-500 overflow-hidden whitespace-pre text-center">{{
controlStore.ServerName
}}</span>
<span
class="col-start-2 col-end-8 text-xs text-left text-gray-100 overflow-hidden whitespace-pre ml-[5px]"
>Mach</span
>
<span
class="col-start-8 col-span-full text-md text-yellow-500 overflow-hidden whitespace-pre"
>{{ controlStore.ServerName }}</span
>
</div>
<div
v-if="controlStore.ipAddress"
class="w-full px-2 h-[25px] grid grid-cols-12 items-center"
class="w-full px-2 h-[25px] grid grid-cols-24 items-center"
@mouseenter="footerStore.cursorLocation = `${machineIp}`"
@mouseleave="footerStore.cursorLocation = ''"
>
<span class="col-start-2 col-span-1 text-xs text-left text-gray-100 overflow-hidden whitespace-pre ml-[5px]"
>IP :</span
<span
class="col-start-2 col-end-7 text-xs text-left text-gray-100 overflow-hidden whitespace-pre ml-[5px]"
>IP
</span>
<span
class="col-start-8 col-end-22 text-sm text-yellow-500 overflow-hidden whitespace-pre"
>{{ controlStore.ipAddress }}</span
>
<span class="col-start-4 col-end-12 text-sm text-yellow-500 overflow-hidden whitespace-pre">{{
controlStore.ipAddress
}}</span>

<img
class="w-7 col-start-12 col-span-1 cursor-pointer hover:scale-110 active:scale-95 transition-all duration-200 ease-in-out"
class="w-7 col-start-23 col-span-full cursor-pointer hover:scale-110 active:scale-95 transition-all duration-200 ease-in-out"
src="/img/icon/service-modals-icons/copy.png"
alt="icon"
@click="copyServerIp"
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<div
class="col-start-4 col-end-7 grid grid-cols-6 gap-1 items-center bg-[#151618] border h-full border-gray-600 rounded-md p-2"
>
<SetupDropdown
:list="props.list"
@select-rename="selectRename"
@confirm-rename="confirmRename"
@select-setup="selectSetup"
@server-view="serverView"
/>
</div>
</template>
<script setup>
import SetupDropdown from "../setups/SetupDropdown.vue";
const props = defineProps({
list: {
type: Array,
required: true,
},
});
const emit = defineEmits(["selectRename", "confirmRename", "selectSetup", "serverView"]);
const selectRename = (setup) => {
emit("selectRename", setup);
};
const confirmRename = () => {
emit("confirmRename");
};
const selectSetup = (setup) => {
emit("selectSetup", setup);
};
const serverView = () => {
emit("serverView");
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<div class="w-full h-full col-start-6 col-span-full flex justify-center items-center">
<div
v-if="setupStore.isRenameSetupActive"
class="w-full h-full col-start-6 col-span-1 flex justify-center items-center bg-teal-800 border border-gray-600 rounded-sm p-[2px]"
@click="confirmRename"
@mouseenter="footerStore.cursorLocation = 'Rename Node Setup'"
@mouseleave="footerStore.cursorLocation = ''"
>
<img
class="w-5 hover:scale-105 active:scale-95 cursor-pointer transition-all duration-150"
src="/img/icon/service-setting-icons/confirm.png"
alt="Avatar"
/>
</div>
<div
v-else
class="w-full h-full col-start-6 col-span-1 flex justify-center items-center bg-[#333539] border border-gray-600 rounded-sm p-[2px]"
:class="{
'pointer-events-none opacity-45 ': setupStore.selectedSetup === null,
}"
@click="renameSetup"
@mouseenter="footerStore.cursorLocation = 'Rename Node Setup'"
@mouseleave="footerStore.cursorLocation = ''"
>
<img
class="w-5 hover:scale-105 active:scale-95 cursor-pointer transition-all duration-150"
src="/img/icon/edit-node-icons/rename.png"
alt="Icon"
@mousedown.prevent
/>
</div>
</div>
</template>

<script setup>
import { useFooter } from "@/store/theFooter";
import { useSetups } from "@/store/setups";
const emit = defineEmits(["renameSetup", "confirmRename"]);
const setupStore = useSetups();
const footerStore = useFooter();
const renameSetup = () => {
emit("renameSetup");
};
const confirmRename = () => {
emit("confirmRename");
};
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
<template>
<div
class="w-full relative col-start-1 col-span-full grid grid-cols-6 gap-x-1"
:class="newHeight"
>
<label
v-if="setupStore.isRenameSetupActive && route.path === '/edit'"
for="rename"
class="w-full h-full col-start-1 col-end-6 text-gray-800 bg-[#232528] rounded-sm border border-gray-600 flex justify-center items-center"
>
<input
id="rename"
v-model="setupStore.setupToRename"
type="text"
class="w-full h-full pl-2 bg-[#232528] text-gray-200 text-xs"
focusable
/>
</label>

<!-- Dropdown toggle button -->

<div
v-else
class="col-start-1 relative p-1 grid rounded-[4px] border border-gray-600"
:class="
route.path === '/edit' ? 'col-end-6 grid-cols-9' : 'col-span-full grid-cols-12'
"
@click="toggleDropdown"
>
<span
v-if="setupStore.selectedSetup !== null && setupStore.selectedSetup?.isActive"
class="col-start-1 col-span-1 w-4 h-4 rounded-full self-center justify-self-center shadow-sm shadow-black"
:class="setupStore.getBGColor(setupStore.selectedSetup?.color)"
></span>
<img
v-else
class="col-start-1 col-span-1 w-4 h-4 rounded-full self-center justify-self-center shadow-sm shadow-black"
src="/img/icon/stereum-icons/stereum-logo.png"
alt="Server View"
/>
<span
class="self-center col-start-2 text-sm font-sans font-[500] overflow-hidden truncate text-gray-200 ml-2"
:class="route.path === '/edit' ? 'col-end-9' : 'col-end-11'"
>{{ getSelectedOption }}</span
>

<svg
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
class="h-3 w-3 text-white self-center col-span-1 transform transition-transform duration-200 ease-in-out"
:class="[
isOpen ? 'rotate-180' : 'rotate-0',
route.path === '/edit' ? 'col-start-9' : 'col-start-12',
]"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
</svg>
</div>

<!-- Dropdown menu -->
<Transition
enter-active-class="transform transition duration-500 ease-custom"
enter-class="-translate-y-1/2 scale-y-0 opacity-0"
enter-to-class="translate-y-0 scale-y-100 opacity-100"
leave-active-class="transform transition duration-300 ease-custom"
leave-class="translate-y-0 scale-y-100 opacity-100"
leave-to-class="-translate-y-1/2 scale-y-0 opacity-0"
>
<div
v-if="isOpen"
class="absolute top-9 z-20 min-h-20 mt-1 origin-top-right rounded-sm shadow-md bg-gray-200 transition-all duration-100 divide-y-2 divide-gray-500 shadow-black"
:class="getDropdownWidth"
@mouseleave="isOpen = false"
>
<div
v-if="setupStore.isConfigViewActive || setupStore.isEditConfigViewActive"
class="p-2 bg-gray-300 capitalize transition-colors duration-300 transform text-[#336666] hover:bg-blue-300 cursor-pointer grid grid-cols-6 items-center"
@click="selectServerView"
>
<img
class="col-start-1 col-span-1 w-5 h-5 rounded-full border border-gray-300 self-center bg-gray-100"
src="/img/icon/stereum-icons/stereum-logo.png"
alt="Node Server View"
/>
<span
class="col-start-2 col-span-full self-center text-left text-sm font-semibold overflow-hidden truncate font-sans"
>Server View</span
>
</div>
<div
v-for="setup in list.filter((s) => s.setupName !== 'commonServices')"
v-show="setup.setupId !== setupStore.selectedSetup?.setupId"
:key="setup.setupName"
class="p-2 bg-gray-300 capitalize transition-colors duration-300 transform text-gray-600 hover:bg-blue-300 hover:text-gray-700 cursor-pointer text-sm font-bold overflow-hidden truncate grid grid-cols-6 gap-x-1"
@click="selectSetup(setup)"
>
<span
class="col-start-1 col-span-1 w-5 h-5 rounded-full border border-gray-300 self-center justify-self-start"
:class="setupStore.getBGColor(setup.color)"
></span>
<span
class="col-start-2 col-span-full self-center text-sm font-bold overflow-hidden truncate font-sans"
>{{ setup.setupName }}</span
>
</div>
</div>
</Transition>

<!-- Rename setup button -->
<!-- Rename Button -->
<RenameSetup
v-if="route.path === '/edit'"
@confirm-rename="confirmRename"
@rename-setup="selectRename"
/>
</div>
</template>
<script setup>
import { useSetups } from "@/store/setups";
import { computed, ref } from "vue";
import { useRoute } from "vue-router";
import RenameSetup from "./RenameSetup.vue";
const { list, newHeight } = defineProps({
list: {
type: Array,
required: true,
},
newHeight: {
type: String,
required: false,
default: "h-full",
},
});
const emit = defineEmits(["selectRename", "selectSetup", "serverView", "confirmRename"]);
const route = useRoute();
const setupStore = useSetups();
const isOpen = ref(false);
const getDropdownWidth = computed(() => {
let width;
if (route.path === "/edit") {
width = "w-40 right-8";
} else if (route.path === "/node") {
width = "w-48 right-0";
} else if (route.path === "/staking") {
width = "w-52 right-1";
} else if (route.path === "/control") {
width = "w-44";
} else {
width = "w-48";
}
return width;
});
const getSelectedOption = computed(() => {
let option;
if (setupStore.selectedSetup === null) {
option = "Server View";
} else {
option = setupStore.selectedSetup?.setupName;
}
return option;
});
// Methods
const toggleDropdown = () => {
isOpen.value = !isOpen.value;
};
const selectRename = () => {
if (setupStore.selectedSetup) {
emit("selectRename", setupStore.selectedSetup);
}
};
const confirmRename = () => {
setupStore.isRenameSetupActive = false;
emit("confirmRename");
};
const selectSetup = (setup) => {
isOpen.value = false;
emit("selectSetup", setup);
};
const selectServerView = () => {
isOpen.value = false;
emit("serverView");
};
</script>
<style scoped>
.ease-custom {
transition-timing-function: cubic-bezier(0.61, -0.53, 0.43, 1.43);
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<template>
<div
class="w-full h-full col-start-1 col-span-full row-start-3 row-span-full grid grid-cols-2 grid-rows-12 items-center"
:class="props.setup.isActive ? 'bg-black opacity-45 z-0 rounded-b-md' : ''"
@pointerdown.prevent.stop
@mousedown.prevent.stop
@mouseenter="
[
(footerStore.cursorLocation = `${props.setup.setupName}`),
(setupStore.isSetupMenuActive = true),
]
"
@mouseleave="footerStore.cursorLocation = ''"
>
<img
class="max-h-[60px] col-start-1 col-span-full row-start-2 row-end-10 justify-self-center self-start mt-1"
:src="matchedNetworkIcon"
alt="icon"
/>

<div
class="col-start-1 col-span-full row-start-10 row-span-full text-[8px] mt-2 text-center font-semibold overflow-hidden whitespace-nowrap truncate flex justify-center items-center text-gray-200"
>
<span>{{ NodeConfigName }}</span>
</div>
</div>
</template>
<script setup>
import { computed } from "vue";
import { useFooter } from "@/store/theFooter";
import { useNodeManage } from "@/store/nodeManage";
import { useSetups } from "../../../../../../store/setups";
const footerStore = useFooter();
const manageStore = useNodeManage();
const setupStore = useSetups();
const props = defineProps({
setup: Object,
});
// find the matching network and its icon
const matchedNetworkIcon = computed(() => {
const matchedNetwork = manageStore.networkList.find(
(network) => network.network === props.setup.network
);
return matchedNetwork ? matchedNetwork.icon : "";
});
const NodeConfigName = computed(() => {
let shortName;
const matchedNetwork = manageStore.networkList.find(
(network) => network.network === props.setup.network
);
if (matchedNetwork?.network === "mainnet") {
shortName = "ETH NODE CONFIG";
} else if (matchedNetwork?.network === "holisky") {
shortName = "HLS NODE CONFIG";
} else if (matchedNetwork?.network === "optimism") {
shortName = "OPITMISM CONFIG";
}
return shortName;
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<template>
<div
class="absolute inset-0 w-full h-full flex justify-center items-center mx-auto mt-2"
@mousedown.prevent
@mouseleave="props.setup.isActive = false"
>
<div
class="w-20 h-20 bg-gray-700 border border-gray-600 p-1 rounded-md gap-1 z-10 grid grid-cols-2 grid-rows-2 items-center shadow-lg shadow-black overflow-hidden"
>
<img
v-for="icon in icons"
:key="icon.name"
class="w-8 col-span-1 bg-gray-900 hover:bg-gray-500 p-1 cursor-pointer active:scale-90 transition duration-200 border border-gray-700 rounded-md"
:src="icon.src"
:alt="`${icon.name} icon`"
@click="handleAction(icon.name)"
@mouseenter="footerStore.cursorLocation = `${icon.tooltip}`"
@mouseleave="footerStore.cursorLocation = ''"
/>
</div>
</div>
</template>

<script setup>
import { useFooter } from "@/store/theFooter";
// import i18n from "@/includes/i18n";
// props & emits
const props = defineProps({
setup: {
type: Object,
required: true,
},
});
const emit = defineEmits(["deleteSetup", "connectSetup", "infoModal", "openConfigs"]);
// const t = i18n.global.t;
//Store
const footerStore = useFooter();
//Methods
const handleAction = (item) => {
const action = icons.find((i) => i.name === item).action;
return action();
};
const deleteSetup = () => {
emit("deleteSetup", props.setup);
footerStore.cursorLocation = "";
};
const connectSetup = () => {
emit("connectSetup", props.setup);
footerStore.cursorLocation = "";
};
const infoModal = () => {
emit("infoModal", props.setup);
footerStore.cursorLocation = "";
};
const openConfigs = () => {
emit("openConfigs", props.setup);
};
// Now reference these functions in the icons array
const icons = [
{
name: "connection",
src: "/img/icon/edit-node-icons/service-connecting.png",
action: connectSetup,
tooltip: "Setup Connection",
},
{
name: "delete",
src: "/img/icon/edit-node-icons/service-delete.png",
tooltip: "Remove Setup",
action: deleteSetup,
},
{
name: "info",
src: "/img/icon/edit-node-icons/service-info.png",
tooltip: "Setup Informations",
action: infoModal,
},
{
name: "open",
src: "/img/icon/edit-node-icons/link.png",
tooltip: "Configs Page",
action: openConfigs,
},
];
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<template>
<div
class="w-[130px] h-[120px] col-span-1 row-span-1 items-center border rounded-md mx-auto relative grid grid-cols-2 grid-rows-12"
:class="getDynamicClasses(props.setup)"
@mouseenter="props.setup.isActive = true"
@mouseleave="props.setup.isActive = false"
>
<div
class="w-full h-full col-start-1 col-span-full row-start-1 row-span-2 text-[10px] text-center font-semibold capitalize overflow-hidden whitespace-nowrap truncate p-1 rounded-t-[0.28rem]"
:class="[textColor, bgColor]"
>
<span>{{ props.setup.setupName }}</span>
</div>

<SetupLayout :setup="props.setup" />
<Transition name="slide-fade">
<SetupMenu
v-if="props.setup.isActive && !props.setup.isRemoveProcessing"
:setup="props.setup"
@delete-setup="deleteSetup"
@connect-setup="connectSetup"
@info-modal="infoModal"
@open-configs="openConfigs"
/>
</Transition>
</div>
</template>

<script setup>
import { computed } from "vue";
import { useSetups } from "../../../../../../store/setups";
import SetupLayout from "./SetupLayout.vue";
import SetupMenu from "./SetupMenu.vue";
// props & emits
const props = defineProps({
setup: Object,
});
const emit = defineEmits(["deleteSetup", "connectSetup", "infoModal", "openConfigs"]);
//Store
const setupStore = useSetups();
// refs
//Computed
const getDynamicClasses = (item) => {
if (item.hasOwnProperty("isRemoveProcessing") && item.isRemoveProcessing) {
return "border bg-red-500 border-white";
} else {
return "border-gray-500";
}
};
const textColor = computed(() => setupStore.getColor(props.setup.color, "text"));
const bgColor = computed(() => setupStore.getColor(props.setup.color, "background"));
//Lifecycle Hooks
//Methods
const deleteSetup = (item) => {
emit("deleteSetup", item);
};
const connectSetup = (item) => {
emit("connectSetup", item);
};
const infoModal = (item) => {
emit("infoModal", item);
};
const openConfigs = (item) => {
emit("openConfigs", item);
};
</script>
<style scoped>
.slide-fade-enter-active {
transition: all 0.3s ease-out;
}
.slide-fade-leave-active {
transition: all 0.5s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter-from,
.slide-fade-leave-to {
transform: translateX(50px);
opacity: 0;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<template>
<div
class="w-full h-full fixed inset-0 flex justify-center items-center rounded-lg z-20"
>
<div class="w-full h-full opacity-70 fixed inset-0 bg-black z-30 rounded-lg"></div>
<div class="w-2/3 flex justify-center items-center p-8 z-50">
<img class="opacity-80" :src="props.anime" alt="Modify Gif" />
</div>
</div>
</template>

<script setup>
const props = defineProps({
anime: {
type: String,
required: true,
},
});
</script>
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { onMounted, computed } from 'vue';
<template>
<div v-if="!manageStore.newConfiguration.length > 0" class="mt-4 flex justify-center items-center">
<div
v-if="!manageStore.newConfiguration.length > 0"
class="mt-4 flex justify-center items-center"
>
<p class="text-md text-gray-400 font-semibold">
{{ $t("editModals.noAvailService") }}
</p>
@@ -11,17 +14,23 @@ import { onMounted, computed } from 'vue';
class="w-1/3 h-[280px] flex flex-col justify-start items-center"
>
<div class="w-full h-5 flex justify-center items-center mt-2">
<span class="text-lg font-semibold text-gray-500">{{ $t("editModals.consensusClients") }}</span>
<span class="text-lg font-semibold text-gray-500">{{
$t("editModals.consensusClients")
}}</span>
</div>
<div
class="w-full h-[250px] overflow-y-auto overflow-x-hidden flex flex-col justify-start items-center mx-auto rounded-lg space-y-2 mt-1"
>
<div
v-for="option in list.filter((e) => e.category === 'consensus')"
v-for="option in list.filter(
(e) =>
e.category === 'consensus' && e.setupId === setupStore.selectedSetup.setupId
)"
:key="option.service"
class="group mx-auto rounded-md cursor-pointer transition duration-200 shadow-xl shadow-[#141516] p-2"
:class="{
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700': option.isConnected,
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700':
option.isConnected,
'bg-[#282a2c] text-teal-600 border-2 border-gray-600 hover:border-teal-600': !option.isConnected,
' w-[190px] h-[55px]': props.client.service === 'SSVNetworkService',
'w-[200px] h-[65px] text-md': props.client.service !== 'SSVNetworkService',
@@ -52,17 +61,23 @@ import { onMounted, computed } from 'vue';
class="w-1/3 h-[280px] flex flex-col justify-start items-center"
>
<div class="w-full h-5 flex justify-center items-center mt-2">
<span class="text-lg font-semibold text-gray-500">{{ $t("editModals.executionClients") }}</span>
<span class="text-lg font-semibold text-gray-500">{{
$t("editModals.executionClients")
}}</span>
</div>
<div
class="w-full h-[250px] overflow-x-hidden overflow-y-auto flex flex-col justify-start items-center mx-auto rounded-lg space-y-2 mt-1"
>
<div
v-for="option in list.filter((e) => e.category === 'execution')"
v-for="option in list.filter(
(e) =>
e.category === 'execution' && e.setupId === setupStore.selectedSetup.setupId
)"
:key="option.service"
class="group mx-auto rounded-md cursor-pointer transition duration-200 shadow-xl shadow-[#141516] p-2"
:class="{
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700': option.isConnected,
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700':
option.isConnected,
'bg-[#282a2c] text-teal-600 border-2 border-gray-600 hover:border-teal-600': !option.isConnected,
' w-[190px] h-[55px]': props.client.service === 'SSVNetworkService',
'w-[200px] h-[65px] text-md': props.client.service !== 'SSVNetworkService',
@@ -93,17 +108,23 @@ import { onMounted, computed } from 'vue';
class="w-1/3 h-[280px] flex flex-col justify-start items-center"
>
<div class="w-full h-5 flex justify-center items-center mt-2">
<span class="text-lg font-semibold text-gray-500">{{ $t("editModals.validatorClients") }}</span>
<span class="text-lg font-semibold text-gray-500">{{
$t("editModals.validatorClients")
}}</span>
</div>
<div
class="w-full h-[250px] flex flex-col justify-start items-center mx-auto rounded-lg space-y-2 mt-1 overflow-x-hidden overflow-y-auto"
>
<div
v-for="option in list.filter((e) => e.category === 'validator')"
v-for="option in list.filter(
(e) =>
e.category === 'validator' && e.setupId === setupStore.selectedSetup.setupId
)"
:key="option.service"
class="group mx-auto rounded-md cursor-pointer transition duration-200 shadow-xl shadow-[#141516] p-2"
:class="{
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700': option.isConnected,
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700':
option.isConnected,
'bg-[#282a2c] text-teal-600 border-2 border-gray-600 hover:border-teal-600': !option.isConnected,
' w-[190px] h-[55px]': props.client.service === 'SSVNetworkService',
'w-[200px] h-[65px] text-md': props.client.service !== 'SSVNetworkService',
@@ -144,7 +165,8 @@ import { onMounted, computed } from 'vue';
:key="option.service"
class="group mx-auto rounded-md cursor-pointer transition duration-200 shadow-xl shadow-[#141516] p-2"
:class="{
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700': option.isConnected,
'bg-teal-600 hover:bg-teal-600 text-gray-200 border-2 border-teal-700':
option.isConnected,
'bg-[#282a2c] text-teal-600 border-2 border-gray-600 hover:border-teal-600': !option.isConnected,
' w-[190px] h-[55px]': props.client.service === 'SSVNetworkService',
'w-[200px] h-[65px] text-md': props.client.service !== 'SSVNetworkService',
@@ -175,6 +197,7 @@ import { onMounted, computed } from 'vue';
<script setup>
import { useNodeManage } from "@/store/nodeManage";
import { onMounted, ref } from "vue";
import { useSetups } from "../../../../../store/setups";
const emit = defineEmits(["select-service"]);
@@ -194,6 +217,7 @@ const props = defineProps({
//Stores
const manageStore = useNodeManage();
const setupStore = useSetups();
//Lifecycle Hooks
onMounted(() => {
@@ -211,11 +235,15 @@ const toggleConnection = (option) => {
} else {
option.isConnected = false;
}
props.properties.executionClients = list.value.filter((e) => e.category === "execution" && e.isConnected);
props.properties.executionClients = list.value.filter(
(e) => e.category === "execution" && e.isConnected
);
props.properties.consensusClients = list.value.filter(
(e) => (e.category === "consensus" || e.service === "CharonService") && e.isConnected
);
props.properties.otherServices = list.value.filter((e) => e.category === "service" && e.isConnected);
props.properties.otherServices = list.value.filter(
(e) => e.category === "service" && e.isConnected
);
};
const getConnectionOptions = () => {
@@ -232,15 +260,19 @@ const getConnectionOptions = () => {
return manageStore.newConfiguration.filter((e) => e.category === "execution");
case "validator":
if (props.client.service === "SSVNetworkService") {
return manageStore.newConfiguration.filter((e) => e.category === "consensus" || e.category === "execution");
return manageStore.newConfiguration.filter(
(e) => e.category === "consensus" || e.category === "execution"
);
}
if (props.client.service === "Web3SignerService") {
return [];
}
if (props.client.service === "CharonService") {
return manageStore.newConfiguration.filter((e) => e.category === "consensus");
}
return manageStore.newConfiguration.filter((e) => e.category === "consensus" || e.service === "CharonService");
return manageStore.newConfiguration.filter(
(e) => e.category === "consensus" || e.service === "CharonService"
);
case "service":
if (props.client.service === "FlashbotsMevBoostService") {
return manageStore.newConfiguration.filter((e) => e.category === "consensus");
@@ -251,7 +283,9 @@ const getConnectionOptions = () => {
);
}
if (props.client.service === "ValidatorEjectorService") {
return manageStore.newConfiguration.filter((e) => /consensus|execution/.test(e.category));
return manageStore.newConfiguration.filter((e) =>
/consensus|execution/.test(e.category)
);
}
break;
default:
@@ -261,7 +295,9 @@ const getConnectionOptions = () => {
const shortID = (client) => {
if (client?.config?.serviceID) {
return client.config.serviceID.slice(0, 8) + "..." + client.config.serviceID.slice(-8);
return (
client.config.serviceID.slice(0, 8) + "..." + client.config.serviceID.slice(-8)
);
}
return client.id;
};
Loading