Priority and exclusive nodes setting implementation

This commit is contained in:
everoddandeven 2024-12-28 19:24:39 +01:00
parent 827f2ff585
commit 4d8487bbcb
4 changed files with 459 additions and 32 deletions

View file

@ -16,9 +16,7 @@ export abstract class BasePageComponent implements AfterContentInit, OnDestroy {
protected subscriptions: Subscription[] = [];
constructor(private navbarService: NavbarService) {
}
constructor(private navbarService: NavbarService) { }
protected setLinks(links: NavbarLink[] = []): void {
this._links = links;
@ -113,6 +111,62 @@ export abstract class BasePageComponent implements AfterContentInit, OnDestroy {
return false;
}
protected getTable(id: string): JQuery<HTMLElement> | undefined {
const initalized: JQuery<HTMLElement> | undefined = this.initializedTables[id];
return initalized;
}
protected getTableSelection<TRow extends { [key: string]: any }>(id: string): TRow[] {
const table = this.getTable(id);
if (!table) {
return [];
}
const result = table.bootstrapTable('getSelections') as TRow[];
if (!result) {
return [];
}
return result;
}
protected removeTableSelection<TRow extends { [key: string]: any }>(id: string, key: string): TRow[] {
const table = this.getTable(id);
if (!table || key === '') {
return [];
}
const selection = this.getTableSelection<TRow>(id);
if (selection.length === 0) {
return [];
}
const firstRow = selection[0];
if (!firstRow[key]) {
console.warn("Cannote remove selection by key: " + key);
return [];
}
const ids: any[] = [];
selection.forEach((s) => {
ids.push(s[key]);
});
table.bootstrapTable('remove', {
field: key,
values: ids
});
return selection;
}
public ngAfterContentInit(): void {
this.navbarService.setLinks(this._links);
}

View file

@ -590,6 +590,100 @@
<input type="number" min="0" class="form-control" id="p2p-bind-port-ipv6" placeholder="18080" [(ngModel)]="currentSettings.p2pBindPortIpv6" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">18080 for mainnet, 28080 for testnet, 38080 for stagenet</small>
</div>
<hr class="my-4">
<h4 class="mb-3">Seed Node</h4>
<div class="col-md-6">
<label for="seed-node-address" class="form-label">Node address</label>
<input type="text" class="form-control" id="seed-node-address" [(ngModel)]="seedNodeAddress" (change)="onSeedNodeChange()" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Connect to a node to retrieve other nodes' addresses, and disconnect</small>
</div>
<div class="col-md-4">
<label for="seed-node-port" class="form-label">Node port</label>
<input type="number" min="0" class="form-control" id="seed-node-port" placeholder="18080" (change)="onSeedNodeChange()" [(ngModel)]="seedNodePort" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Specify node port</small>
</div>
<hr class="my-4">
<h4 class="mb-3">Exclusive Nodes</h4>
<div class="col-md-12">
<button [disabled]="!canRemoveExclusiveNodes" id="remove-selected-exclusive-node" type="button" class="btn btn-primary" (click)="removeSelectedExclusiveNodes()"><i class="bi bi-trash-fill"></i></button>
<br><br>
<table
id="exclusiveNodesTable"
data-toggle="exclusiveNodesTable"
data-height="300"
data-card-view="true">
<thead>
<tr>
<th data-field="state" data-checkbox="true">ID</th>
<th data-field="address">Address</th>
<th data-field="port">Port</th>
</tr>
</thead>
</table>
</div>
<div class="col-md-6">
<label for="add-exlcusive-node-address" class="form-label">Node address</label>
<input type="text" class="form-control" id="add-exlcusive-node-address" [(ngModel)]="exclusiveNodeAddress" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Specify a node to connect to only</small>
</div>
<div class="col-md-4">
<label for="add-exlcusive-node-port" class="form-label">Node port</label>
<input type="number" min="0" class="form-control" id="add-exlcusive-node-port" placeholder="18080" [(ngModel)]="exclusiveNodePort" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Specify node port</small>
</div>
<div class="col-md-2">
<label class="form-label">Add</label>
<button [disabled]="!canAddExclusiveNode" type="button" class="form-control btn btn-primary" (click)="addExclusiveNode()"><i class="bi bi-plus-square"></i></button>
</div>
<hr class="my-4">
<h4 class="mb-3">Priority Nodes</h4>
<div class="col-md-12">
<button [disabled]="!canRemovePriorityNodes" id="remove-selected-priority-node" type="button" class="btn btn-primary" (click)="removeSelectedPriorityNodes()"><i class="bi bi-trash-fill"></i></button>
<br><br>
<table
id="priorityNodesTable"
data-toggle="priorityNodesTable"
data-height="300"
data-card-view="true">
<thead>
<tr>
<th data-field="state" data-checkbox="true">ID</th>
<th data-field="address">Address</th>
<th data-field="port">Port</th>
</tr>
</thead>
</table>
</div>
<div class="col-md-6">
<label for="add-priority-node-address" class="form-label">Node address</label>
<input type="text" class="form-control" id="add-priority-node-address" placeholder="" [(ngModel)]="priorityNodeAddress" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Specify a node to connect to only</small>
</div>
<div class="col-md-4">
<label for="add-priority-node-port" class="form-label">Node port</label>
<input type="number" min="0" class="form-control" id="add-priority-node-port" placeholder="18080" [(ngModel)]="priorityNodePort" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Specify node port</small>
</div>
<div class="col-md-2">
<label class="form-label">Add</label>
<button [disabled]="!canAddPriorityNode" type="button" class="form-control btn btn-primary" (click)="addPriorityNode()"><i class="bi bi-plus-square"></i></button>
</div>
</div>

View file

@ -1,39 +1,32 @@
import { Component, NgZone } from '@angular/core';
import { AfterViewInit, Component, NgZone } from '@angular/core';
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { DaemonSettings } from '../../../common/DaemonSettings';
import { DaemonService } from '../../core/services/daemon/daemon.service';
import { ElectronService } from '../../core/services';
import { DaemonSettingsError } from '../../../common';
import { BasePageComponent } from '../base-page/base-page.component';
import { NavbarService } from '../../shared/components';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrl: './settings.component.scss'
})
export class SettingsComponent {
export class SettingsComponent extends BasePageComponent implements AfterViewInit {
public readonly navbarLinks: NavbarLink[];
private removingExclusiveNodes: boolean = false;
private removingPriorityNodes: boolean = false;
private addingExclusiveNode: boolean = false;
private addingPriorityNode: boolean = false;
private originalSettings: DaemonSettings;
public currentSettings: DaemonSettings;
public banListUrl: string = '';
public remoteBanList: boolean = false;
public get validBanListUrl(): boolean {
if (this.banListUrl == '') {
return false;
}
try {
new URL(this.banListUrl);
return true;
}
catch {
return false;
}
}
public savingChanges: boolean = false;
public savingChangesError = ``;
public savingChangesSuccess: boolean = false;
@ -49,12 +42,150 @@ export class SettingsComponent {
public databaseSyncMode: 'sync' | 'async' = 'async';
public databaseSyncNBytesOrBlocks: number = 250000000;
public databaseSyncNPerMode: 'bytes' | 'blocks' = 'bytes';
public isPortable: boolean = true;
public downloadingBanListFile: boolean = false;
public exclusiveNodeAddress: string = '';
public exclusiveNodePort: number = 0;
public priorityNodeAddress: string = '';
public priorityNodePort: number = 0;
public seedNodeAddress: string = '';
public seedNodePort: number = 0;
private get seedNode(): string {
return `${this.seedNodeAddress}:${this.seedNodePort}`;
}
private get exclusiveNodes(): NodeInfo[] {
const result: { address: string, port: number }[] = [];
this.currentSettings.exclusiveNodes.forEach((en) => {
const components = en.split(":");
if (components.length !== 2) {
return;
}
const [ address, strPort ] = components;
try {
const port = parseInt(strPort);
result.push({ address: address, port: port });
}
catch {
return;
}
});
return result;
}
private get priorityNodes(): NodeInfo[] {
const result: { address: string, port: number }[] = [];
this.currentSettings.priorityNodes.forEach((en) => {
const components = en.split(":");
if (components.length !== 2) {
return;
}
const [ address, strPort ] = components;
try {
const port = parseInt(strPort);
result.push({ address: address, port: port });
}
catch {
return;
}
});
return result;
}
private get dbSyncMode(): string {
return `${this.databaseSyncSpeed}:${this.databaseSyncMode}:${this.databaseSyncNBytesOrBlocks}${this.databaseSyncNPerMode}`;
}
constructor(private daemonService: DaemonService, private electronService: ElectronService, private ngZone: NgZone) {
public get validBanListUrl(): boolean {
if (this.banListUrl == '') {
return false;
}
try {
new URL(this.banListUrl);
return true;
}
catch {
return false;
}
}
public get canAddExclusiveNode(): boolean {
if (this.addingExclusiveNode) {
return false;
}
if (this.exclusiveNodePort <= 0) {
return false;
}
if (this.exclusiveNodeAddress.trim().replace(' ', '') === '' || this.exclusiveNodeAddress.includes(":")) {
return false;
}
if (this.exclusiveNodes.find((en) => en.address === this.exclusiveNodeAddress && en.port === this.exclusiveNodePort)) {
return false;
}
return true;
}
public get canRemoveExclusiveNodes(): boolean {
if (this.removingExclusiveNodes || this.exclusiveNodes.length === 0) {
return false;
}
return true;
}
public get canRemovePriorityNodes(): boolean {
if (this.removingPriorityNodes || this.priorityNodes.length === 0) {
return false;
}
return true;
}
public get canAddPriorityNode(): boolean {
if (this.addingPriorityNode) {
return false;
}
if (this.priorityNodePort <= 0) {
return false;
}
if (this.priorityNodeAddress.trim().replace(' ', '') === '' || this.priorityNodeAddress.includes(":")) {
return false;
}
if (this.priorityNodes.find((en) => en.address === this.priorityNodeAddress && en.port === this.priorityNodePort)) {
return false;
}
return true;
}
constructor(private daemonService: DaemonService, private electronService: ElectronService, navbarService: NavbarService, private ngZone: NgZone) {
super(navbarService);
this.loading = true;
this.navbarLinks = [
@ -86,7 +217,9 @@ export class SettingsComponent {
});
}
public isPortable: boolean = true;
public ngAfterViewInit(): void {
this.loadTables();
}
public refreshSyncMode(): void {
setTimeout(() => {
@ -125,6 +258,22 @@ export class SettingsComponent {
private async load(): Promise<void> {
this.originalSettings = await this.daemonService.getSettings();
this.currentSettings = this.originalSettings.clone();
if (this.seedNode !== '') {
const components = this.seedNode.split(":");
if (components.length >= 2) {
const [node, strPort] = components;
this.seedNodeAddress = node;
this.seedNodePort = parseInt(strPort);
}
else if (components.length === 1)
{
this.seedNodeAddress = components[0];
}
}
this.initSyncMode();
this.loading = false;
@ -133,6 +282,23 @@ export class SettingsComponent {
this.networkType = this.currentSettings.mainnet ? 'mainnet' : this.currentSettings.testnet ? 'testnet' : this.currentSettings.stagenet ? 'stagenet' : 'mainnet';
}
private loadTables(): void {
setTimeout(() => {
this.ngZone.run(() => {
this.loadExclusiveNodesTable();
this.loadPriorityNodesTable();
});
}, 500);
}
private loadExclusiveNodesTable(loading: boolean = false): void {
this.loadTable('exclusiveNodesTable', this.exclusiveNodes, loading);
}
private loadPriorityNodesTable(loading: boolean = false): void {
this.loadTable('priorityNodesTable', this.priorityNodes, loading);
}
public get modified(): boolean {
if (!this.currentSettings.equals(this.originalSettings)) {
return true;
@ -145,6 +311,15 @@ export class SettingsComponent {
return !this.modified || this.daemonService.restarting || this.daemonService.starting || this.daemonService.stopping;
}
public onSeedNodeChange() {
if (this.seedNodePort >= 0) {
this.currentSettings.seedNode = `${this.seedNodeAddress}:${this.seedNodePort}`;
}
else {
this.currentSettings.seedNode = this.seedNodeAddress;
}
}
public OnOfflineChange() {
this.currentSettings.offline = !this.currentSettings.offline;
}
@ -384,8 +559,6 @@ export class SettingsComponent {
this.currentSettings.banList = '';
}
public downloadingBanListFile: boolean = false;
public async downloadBanListFile(): Promise<void> {
if (!this.remoteBanList) {
return;
@ -535,4 +708,85 @@ export class SettingsComponent {
public removeLogFile(): void {
this.currentSettings.logFile = '';
}
public addExclusiveNode(): void {
if (!this.canAddExclusiveNode) {
return;
}
this.addingExclusiveNode = true;
try {
const node = `${this.exclusiveNodeAddress}:${this.exclusiveNodePort}`;
this.currentSettings.addExclusiveNode(node);
this.loadExclusiveNodesTable();
}
catch (error: any) {
console.error(error);
}
this.addingExclusiveNode = false;
}
public removeSelectedExclusiveNodes(): void {
if (!this.canRemoveExclusiveNodes) {
return;
}
this.removingExclusiveNodes = true;
const removed = this.removeTableSelection<NodeInfo>('exclusiveNodesTable', 'address');
removed.forEach((r) => {
const node = r.port >= 0 ? `${r.address}:${r.port}` : r.address;
this.currentSettings.removeExclusiveNode(node);
});
this.loadExclusiveNodesTable();
this.removingExclusiveNodes = false;
}
public addPriorityNode(): void {
if (!this.canAddPriorityNode) {
return;
}
this.addingPriorityNode = true;
try {
const node = `${this.priorityNodeAddress}:${this.priorityNodePort}`;
this.currentSettings.addPriorityNode(node);
this.loadPriorityNodesTable();
}
catch (error: any) {
console.error(error);
}
this.addingPriorityNode = false;
}
public removeSelectedPriorityNodes(): void {
if (!this.canRemovePriorityNodes) {
return;
}
this.removingPriorityNodes = true;
const removed = this.removeTableSelection<NodeInfo>('priorityNodesTable', 'address');
removed.forEach((r) => {
const node = r.port >= 0 ? `${r.address}:${r.port}` : r.address;
this.currentSettings.removePriorityNode(node);
});
this.loadPriorityNodesTable();
this.removingPriorityNodes = false;
}
}
interface NodeInfo { address: string, port: number };

View file

@ -94,7 +94,7 @@ export class DaemonSettings {
public addPeer: string = '';
//public addPriorityNode: string = '';
//public addExclusiveNode: string = '';
public exlusiveNodes: string[] = [];
public exclusiveNodes: string[] = [];
public priorityNodes: string[] = [];
public seedNode: string = '';
@ -155,7 +155,7 @@ export class DaemonSettings {
}
public get hasExclusiveNodes(): boolean {
return this.exlusiveNodes.length > 0;
return this.exclusiveNodes.length > 0;
}
public get hasPriorityNodes(): boolean {
@ -163,15 +163,15 @@ export class DaemonSettings {
}
public addExclusiveNode(node: string): void {
if (this.exlusiveNodes.includes(node)) {
if (this.exclusiveNodes.includes(node)) {
throw new DaemonSettingsDuplicateExclusiveNodeError(node);
}
this.exlusiveNodes.push(node);
this.exclusiveNodes.push(node);
}
public removeExclusiveNode(node: string): void {
this.exlusiveNodes = this.exlusiveNodes.filter((n) => n !== node);
this.exclusiveNodes = this.exclusiveNodes.filter((n) => n !== node);
}
public addPriorityNode(node: string): void {
@ -179,7 +179,7 @@ export class DaemonSettings {
throw new DaemonSettingsDuplicatePriorityNodeError(node);
}
this.exlusiveNodes.push(node);
this.priorityNodes.push(node);
}
public removePriorityNode(node: string): void {
@ -230,15 +230,40 @@ export class DaemonSettings {
// Se una chiave di obj1 non esiste in obj2, non sono uguali
if (!keys2.includes(key)) return false;
if (key == 'exclusiveNodes' && obj1 instanceof DaemonSettings && obj2 instanceof DaemonSettings) {
if (obj1.exclusiveNodes.length !== obj2.exclusiveNodes.length) return false;
else {
for(const en of obj1.exclusiveNodes) {
if (!obj2.exclusiveNodes.includes(en)) return false;
}
}
}
else if (key == 'priorityNodes' && obj1 instanceof DaemonSettings && obj2 instanceof DaemonSettings) {
if (obj1.priorityNodes.length !== obj2.priorityNodes.length) return false;
else {
for(const en of obj1.priorityNodes) {
if (!obj2.priorityNodes.includes(en)) return false;
}
}
}
else if (!this.deepEqual(obj1[key], obj2[key])) return false;
// Se il valore della proprietà non è uguale, effettua un confronto ricorsivo
if (!this.deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
public clone(): DaemonSettings {
return Object.assign(new DaemonSettings(), this);
const result = Object.assign(new DaemonSettings(), this);
result.exclusiveNodes = [];
result.priorityNodes = [];
this.exclusiveNodes.forEach((en) => result.addExclusiveNode(en));
this.priorityNodes.forEach((pn) => result.addPriorityNode(pn));
return result;
}
public static parse(data: any): DaemonSettings {
@ -473,7 +498,7 @@ export class DaemonSettings {
if (this.allowLocalIp) options.push(`--allow-local-ip`);
if (this.addPeer != '') options.push('--add-peer', this.addPeer);
if (this.hasPriorityNodes) this.priorityNodes.forEach((node) => options.push(`--add-priority-node`, node));
if (this.hasExclusiveNodes) this.exlusiveNodes.forEach((node) => options.push(`--add-exlcusive-node`, node));
if (this.hasExclusiveNodes) this.exclusiveNodes.forEach((node) => options.push(`--add-exlcusive-node`, node));
if (this.seedNode != '') options.push(`--seed-node`, this.seedNode);
if (this.txProxy != '') options.push(`--tx-proxy`, this.txProxy);
if (this.anonymousInbound != '') options.push(`--anonymous-inbound`, this.anonymousInbound);