Merge branch 'master' into nymkappa/feature/automatic-block-reindexing

This commit is contained in:
wiz 2022-07-07 20:55:08 +02:00 committed by GitHub
commit 567d4aebbc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1204 additions and 69 deletions

View File

@ -34,7 +34,7 @@ jobs:
- name: Install (Prod dependencies only)
if: ${{ matrix.flavor == 'prod'}}
run: npm ci --prod --no-optional
run: npm ci --omit=dev --omit=optional
working-directory: ${{ matrix.flavor }}/backend
- name: Lint
@ -70,7 +70,7 @@ jobs:
registry-url: 'https://registry.npmjs.org'
- name: Install (Prod dependencies only)
run: npm ci --prod --no-optional
run: npm ci --omit=dev --omit=optional
if: ${{ matrix.flavor == 'prod'}}
working-directory: ${{ matrix.flavor }}/frontend

View File

@ -8,7 +8,7 @@ COPY . .
RUN apt-get update
RUN apt-get install -y build-essential python3 pkg-config
RUN npm install
RUN npm install --omit=dev --omit=optional
RUN npm run build
FROM node:16.15.0-buster-slim

View File

@ -8,7 +8,7 @@ WORKDIR /build
COPY . .
RUN apt-get update
RUN apt-get install -y build-essential rsync
RUN npm i
RUN npm install --omit=dev --omit=optional
RUN npm run build
FROM nginx:1.17.8-alpine

View File

@ -36,7 +36,6 @@
"echarts": "~5.3.2",
"express": "^4.17.1",
"lightweight-charts": "~3.8.0",
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-echarts": "8.0.1",
"ngx-infinite-scroll": "^10.0.1",
"qrcode": "1.5.0",
@ -12788,19 +12787,6 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"node_modules/ngx-bootrap-multiselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^10.0.6",
"@angular/core": "^10.0.6",
"@angular/forms": "^10.0.6"
}
},
"node_modules/ngx-echarts": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
@ -27418,14 +27404,6 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"ngx-bootrap-multiselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-echarts": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",

View File

@ -90,7 +90,6 @@
"echarts": "~5.3.2",
"express": "^4.17.1",
"lightweight-charts": "~3.8.0",
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-echarts": "8.0.1",
"ngx-infinite-scroll": "^10.0.1",
"qrcode": "1.5.0",

View File

@ -3,7 +3,7 @@
<div class="d-block float-right" id="filter">
<form [formGroup]="radioGroupForm">
<ngx-bootrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootrap-multiselect>
<ngx-bootstrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootstrap-multiselect>
</form>
</div>

View File

@ -7,7 +7,7 @@ import { BisqApiService } from '../bisq-api.service';
import { SeoService } from 'src/app/services/seo.service';
import { FormGroup, FormBuilder } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'src/app/components/ngx-bootstrap-multiselect/types'
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({

View File

@ -1,7 +1,6 @@
import { NgModule } from '@angular/core';
import { BisqRoutingModule } from './bisq.routing.module';
import { SharedModule } from '../shared/shared.module';
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
@ -24,6 +23,10 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
import { CommonModule } from '@angular/common';
import { AutofocusDirective } from '../components/ngx-bootstrap-multiselect/autofocus.directive';
import { MultiSelectSearchFilter } from '../components/ngx-bootstrap-multiselect/search-filter.pipe';
import { OffClickDirective } from '../components/ngx-bootstrap-multiselect/off-click.directive';
import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-multiselect/ngx-bootstrap-multiselect.component';
@NgModule({
declarations: [
@ -44,16 +47,21 @@ import { CommonModule } from '@angular/common';
BisqMarketComponent,
BisqTradesComponent,
BisqMainDashboardComponent,
NgxDropdownMultiselectComponent,
AutofocusDirective,
OffClickDirective,
],
imports: [
CommonModule,
BisqRoutingModule,
SharedModule,
FontAwesomeModule,
NgxBootstrapMultiselectModule,
],
providers: [
BisqApiService,
MultiSelectSearchFilter,
AutofocusDirective,
OffClickDirective,
]
})
export class BisqModule {

View File

@ -0,0 +1,41 @@
import { Directive, ElementRef, Host, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
@Directive({
selector: '[ssAutofocus]'
})
export class AutofocusDirective implements OnInit, OnChanges {
/**
* Will set focus if set to falsy value or not set at all
*/
@Input() ssAutofocus: any;
get element(): { focus?: Function } {
return this.elemRef.nativeElement;
}
constructor(
@Host() private elemRef: ElementRef,
) { }
ngOnInit() {
this.focus();
}
ngOnChanges(changes: SimpleChanges) {
const ssAutofocusChange = changes.ssAutofocus;
if (ssAutofocusChange && !ssAutofocusChange.isFirstChange()) {
this.focus();
}
}
focus() {
if (this.ssAutofocus) {
return;
}
this.element.focus && this.element.focus();
}
}

View File

@ -0,0 +1,48 @@
a {
outline: none !important;
}
.dropdown-inline {
display: inline-block;
}
.dropdown-toggle .caret {
margin-left: 4px;
white-space: nowrap;
display: inline-block;
}
.chunkydropdown-menu {
min-width: 20em;
}
.chunkyrow {
line-height: 2;
margin-left: 1em;
font-size: 2em;
}
.slider {
width:3.8em;
height:3.8em;
display:block;
-webkit-transition: all 0.125s linear;
-moz-transition: all 0.125s linear;
-o-transition: all 0.125s linear;
transition: all 0.125s linear;
margin-left: 0.125em;
margin-top: auto;
}
.slideron {
margin-left: 1.35em;
}
.content_wrapper{
display: table-cell;
vertical-align: middle;
}
.search-container {
padding: 0px 5px 5px 5px;
}

View File

@ -0,0 +1,72 @@
<div *ngIf="options" class="dropdown" [ngClass]="settings.containerClasses" [class.open]="isVisible" (offClick)="clickedOutside()">
<button type="button" class="dropdown-toggle" [ngClass]="settings.buttonClasses" (click)="toggleDropdown($event)" [disabled]="disabled"
[ssAutofocus]="!focusBack">
{{ title }}
<span class="caret"></span>
</button>
<div #scroller *ngIf="isVisible" class="dropdown-menu" [ngClass]="{'chunkydropdown-menu': settings.checkedStyle == 'visual' }"
(scroll)="settings.isLazyLoad ? checkScrollPosition($event) : null" (wheel)="settings.stopScrollPropagation ? checkScrollPropagation($event, scroller) : null"
[class.pull-right]="settings.pullRight" [class.dropdown-menu-right]="settings.pullRight" [style.max-height]="settings.maxHeight"
style="display: block; height: auto; overflow-y: auto;" (keydown.tab)="focusItem(1, $event)" (keydown.shift.tab)="focusItem(-1, $event)">
<div class="input-group search-container" *ngIf="settings.enableSearch && (renderFilteredOptions.length > 1 || filterControl.value.length > 0)">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">
<i class="fa fa-search" aria-hidden="true"></i>
</span>
</div>
<input type="text" class="form-control" ssAutofocus [formControl]="filterControl" [placeholder]="texts.searchPlaceholder"
class="form-control">
<div class="input-group-append" *ngIf="filterControl.value.length>0">
<button class="btn btn-default btn-secondary" type="button" (click)="clearSearch($event)">
<i class="fa fa-times"></i>
</button>
</div>
</div>
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-check" *ngIf="settings.showCheckAll && !disabledSelection && renderFilteredOptions.length > 1"
(click)="checkAll()">
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-ok': settings.checkedStyle !== 'fontawesome','fa fa-check': settings.checkedStyle === 'fontawesome'}"></span></span>
{{ texts.checkAll }}
</a>
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-uncheck" *ngIf="settings.showUncheckAll && !disabledSelection && renderFilteredOptions.length > 1"
(click)="uncheckAll()">
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-remove': settings.checkedStyle !== 'fontawesome','fa fa-times': settings.checkedStyle === 'fontawesome'}"></span></span>
{{ texts.uncheckAll }}
</a>
<a *ngIf="settings.showCheckAll || settings.showUncheckAll" href="javascript:;" class="dropdown-divider divider"></a>
<a *ngIf="!renderItems" href="javascript:;" class="dropdown-item empty">{{ texts.searchNoRenderText }}</a>
<a *ngIf="renderItems && !renderFilteredOptions.length" href="javascript:;" class="dropdown-item empty">{{ texts.searchEmptyResult }}</a>
<a class="dropdown-item" href="javascript:;" *ngFor="let option of renderFilteredOptions; trackBy: trackById" [class.active]="isSelected(option)"
[ngStyle]="getItemStyle(option)" [ngClass]="option.classes" [class.dropdown-header]="option.isLabel" [ssAutofocus]="option !== focusedItem"
tabindex="-1" (click)="setSelected($event, option)" (keydown.space)="setSelected($event, option)" (keydown.enter)="setSelected($event, option)">
<span *ngIf="!option.isLabel; else label" role="menuitem" tabindex="-1" [style.padding-left]="this.parents.length>0&&this.parents.indexOf(option.id)<0&&'30px'"
[ngStyle]="getItemStyleSelectionDisabled()">
<ng-container [ngSwitch]="settings.checkedStyle">
<input *ngSwitchCase="'checkboxes'" type="checkbox" [checked]="isSelected(option)" (click)="preventCheckboxCheck($event, option)"
[disabled]="isCheckboxDisabled(option)" [ngStyle]="getItemStyleSelectionDisabled()" />
<span *ngSwitchCase="'glyphicon'" style="width: 16px;" class="glyphicon" [class.glyphicon-ok]="isSelected(option)" [class.glyphicon-lock]="isCheckboxDisabled(option)"></span>
<span *ngSwitchCase="'fontawesome'" style="width: 16px;display: inline-block;">
<span *ngIf="isSelected(option)"><i class="fa fa-check" aria-hidden="true"></i></span>
<span *ngIf="isCheckboxDisabled(option)"><i class="fa fa-lock" aria-hidden="true"></i></span>
</span>
<span *ngSwitchCase="'visual'" style="display:block;float:left; border-radius: 0.2em; border: 0.1em solid rgba(44, 44, 44, 0.63);background:rgba(0, 0, 0, 0.1);width: 5.5em;">
<div class="slider" [ngClass]="{'slideron': isSelected(option)}">
<img *ngIf="option.image != null" [src]="option.image" style="height: 100%; width: 100%; object-fit: contain" />
<div *ngIf="option.image == null" style="height: 100%; width: 100%;text-align: center; display: table; background-color:rgba(0, 0, 0, 0.74)">
<div class="content_wrapper">
<span style="font-size:3em;color:white" class="glyphicon glyphicon-eye-close"></span>
</div>
</div>
</div>
</span>
</ng-container>
<span [ngClass]="{'chunkyrow': settings.checkedStyle == 'visual' }" [class.disabled]="isCheckboxDisabled(option)" [ngClass]="settings.itemClasses"
[style.font-weight]="this.parents.indexOf(option.id)>=0?'bold':'normal'">
{{ option.name }}
</span>
</span>
<ng-template #label>
<span [class.disabled]="isCheckboxDisabled(option)">{{ option.name }}</span>
</ng-template>
</a>
</div>
</div>

View File

@ -0,0 +1,710 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DoCheck,
EventEmitter,
forwardRef,
Input,
IterableDiffers,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
FormControl,
NG_VALUE_ACCESSOR,
Validator,
} from '@angular/forms';
import { takeUntil } from 'rxjs/operators';
import { MultiSelectSearchFilter } from './search-filter.pipe';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts, } from './types';
import { Subject, Observable } from 'rxjs';
const MULTISELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgxDropdownMultiselectComponent),
multi: true,
};
// tslint:disable-next-line: no-conflicting-lifecycle
@Component({
selector: 'ngx-bootstrap-multiselect',
templateUrl: './ngx-bootstrap-multiselect.component.html',
styleUrls: ['./ngx-bootstrap-multiselect.component.css'],
providers: [MULTISELECT_VALUE_ACCESSOR, MultiSelectSearchFilter],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NgxDropdownMultiselectComponent implements OnInit,
OnChanges,
DoCheck,
OnDestroy,
ControlValueAccessor,
Validator {
private localIsVisible = false;
private workerDocClicked = false;
filterControl: FormControl = this.fb.control('');
@Input() options: Array<IMultiSelectOption>;
@Input() settings: IMultiSelectSettings;
@Input() texts: IMultiSelectTexts;
@Input() disabled = false;
@Input() disabledSelection = false;
@Input() searchFunction: (str: string) => RegExp = this._escapeRegExp;
@Output() selectionLimitReached = new EventEmitter();
@Output() dropdownClosed = new EventEmitter();
@Output() dropdownOpened = new EventEmitter();
@Output() added = new EventEmitter();
@Output() removed = new EventEmitter();
@Output() lazyLoad = new EventEmitter();
@Output() filter: Observable<string> = this.filterControl.valueChanges;
get focusBack(): boolean {
return this.settings.focusBack && this._focusBack;
}
destroyed$ = new Subject<any>();
filteredOptions: IMultiSelectOption[] = [];
lazyLoadOptions: IMultiSelectOption[] = [];
renderFilteredOptions: IMultiSelectOption[] = [];
model: any[] = [];
prevModel: any[] = [];
parents: any[];
title: string;
differ: any;
numSelected = 0;
set isVisible(val: boolean) {
this.localIsVisible = val;
this.workerDocClicked = val ? false : this.workerDocClicked;
}
get isVisible(): boolean {
return this.localIsVisible;
}
renderItems = true;
checkAllSearchRegister = new Set();
checkAllStatus = false;
loadedValueIds = [];
_focusBack = false;
focusedItem: IMultiSelectOption | undefined;
defaultSettings: IMultiSelectSettings = {
closeOnClickOutside: true,
pullRight: false,
enableSearch: false,
searchRenderLimit: 0,
searchRenderAfter: 1,
searchMaxLimit: 0,
searchMaxRenderedItems: 0,
checkedStyle: 'checkboxes',
buttonClasses: 'btn btn-primary dropdown-toggle',
containerClasses: 'dropdown-inline',
selectionLimit: 0,
minSelectionLimit: 0,
closeOnSelect: false,
autoUnselect: false,
showCheckAll: false,
showUncheckAll: false,
fixedTitle: false,
dynamicTitleMaxItems: 3,
maxHeight: '300px',
isLazyLoad: false,
stopScrollPropagation: false,
loadViewDistance: 1,
selectAddedValues: false,
ignoreLabels: false,
maintainSelectionOrderInTitle: false,
focusBack: true
};
defaultTexts: IMultiSelectTexts = {
checkAll: 'Select all',
uncheckAll: 'Unselect all',
checked: 'selected',
checkedPlural: 'selected',
searchPlaceholder: 'Search...',
searchEmptyResult: 'Nothing found...',
searchNoRenderText: 'Type in search box to see results...',
defaultTitle: 'Select',
allSelected: 'All selected',
};
get searchLimit(): number | undefined {
return this.settings.searchRenderLimit;
}
get searchRenderAfter(): number | undefined {
return this.settings.searchRenderAfter;
}
get searchLimitApplied(): boolean {
return this.searchLimit > 0 && this.options.length > this.searchLimit;
}
constructor(
private fb: FormBuilder,
private searchFilter: MultiSelectSearchFilter,
differs: IterableDiffers,
private cdRef: ChangeDetectorRef
) {
this.differ = differs.find([]).create(null);
this.settings = this.defaultSettings;
this.texts = this.defaultTexts;
}
clickedOutside(): void {
if (!this.isVisible || !this.settings.closeOnClickOutside) { return; }
this.isVisible = false;
this._focusBack = true;
this.dropdownClosed.emit();
}
getItemStyle(option: IMultiSelectOption): any {
const style = {};
if (!option.isLabel) {
style['cursor'] = 'pointer';
}
if (option.disabled) {
style['cursor'] = 'default';
}
}
getItemStyleSelectionDisabled(): any {
if (this.disabledSelection) {
return { cursor: 'default' };
}
}
ngOnInit(): void {
this.title = this.texts.defaultTitle || '';
this.filterControl.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(() => {
this.updateRenderItems();
if (this.settings.isLazyLoad) {
this.load();
}
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes['options']) {
this.options = this.options || [];
this.parents = this.options
.filter(option => typeof option.parentId === 'number')
.map(option => option.parentId);
this.updateRenderItems();
if (
this.settings.isLazyLoad &&
this.settings.selectAddedValues &&
this.loadedValueIds.length === 0
) {
this.loadedValueIds = this.loadedValueIds.concat(
changes.options.currentValue.map(value => value.id)
);
}
if (
this.settings.isLazyLoad &&
this.settings.selectAddedValues &&
changes.options.previousValue
) {
const addedValues = changes.options.currentValue.filter(
value => this.loadedValueIds.indexOf(value.id) === -1
);
this.loadedValueIds.concat(addedValues.map(value => value.id));
if (this.checkAllStatus) {
this.addChecks(addedValues);
} else if (this.checkAllSearchRegister.size > 0) {
this.checkAllSearchRegister.forEach((searchValue: string) =>
this.addChecks(this.applyFilters(addedValues, searchValue))
);
}
}
if (this.texts) {
this.updateTitle();
}
this.fireModelChange();
}
if (changes['settings']) {
this.settings = { ...this.defaultSettings, ...this.settings };
}
if (changes['texts']) {
this.texts = { ...this.defaultTexts, ...this.texts };
if (!changes['texts'].isFirstChange()) { this.updateTitle(); }
}
}
ngOnDestroy() {
this.destroyed$.next(false);
}
updateRenderItems() {
this.renderItems =
!this.searchLimitApplied ||
this.filterControl.value.length >= this.searchRenderAfter;
this.filteredOptions = this.applyFilters(
this.options,
this.settings.isLazyLoad ? '' : this.filterControl.value
);
this.renderFilteredOptions = this.renderItems ? this.filteredOptions : [];
this.focusedItem = undefined;
}
applyFilters(options: IMultiSelectOption[], value: string): IMultiSelectOption[] {
return this.searchFilter.transform(
options,
value,
this.settings.searchMaxLimit,
this.settings.searchMaxRenderedItems,
this.searchFunction
);
}
fireModelChange(): void {
if (this.model != this.prevModel) {
this.prevModel = this.model;
this.onModelChange(this.model);
this.onModelTouched();
this.cdRef.markForCheck();
}
}
onModelChange: Function = (_: any) => { };
onModelTouched: Function = () => { };
writeValue(value: any): void {
if (value !== undefined && value !== null) {
this.model = Array.isArray(value) ? value : [value];
this.ngDoCheck();
} else {
this.model = [];
}
}
registerOnChange(fn: Function): void {
this.onModelChange = fn;
}
registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
ngDoCheck() {
const changes = this.differ.diff(this.model);
if (changes) {
this.updateNumSelected();
this.updateTitle();
}
}
validate(_c: AbstractControl): { [key: string]: any } {
if (this.model && this.model.length) {
return {
required: {
valid: false
}
};
}
if (this.options.filter(o => this.model.indexOf(o.id) && !o.disabled).length === 0) {
return {
selection: {
valid: false
}
};
}
return null;
}
registerOnValidatorChange(_fn: () => void): void {
throw new Error('Method not implemented.');
}
clearSearch(event: Event) {
this.maybeStopPropagation(event);
this.filterControl.setValue('');
}
toggleDropdown(e?: Event) {
if (this.isVisible) {
this._focusBack = true;
}
this.isVisible = !this.isVisible;
this.isVisible ? this.dropdownOpened.emit() : this.dropdownClosed.emit();
this.focusedItem = undefined;
}
closeDropdown(e?: Event) {
this.isVisible = true;
this.toggleDropdown(e);
}
isSelected(option: IMultiSelectOption): boolean {
return this.model && this.model.indexOf(option.id) > -1;
}
setSelected(_event: Event, option: IMultiSelectOption) {
if (option.isLabel) {
return;
}
if (option.disabled) {
return;
}
if (this.disabledSelection) {
return;
}
setTimeout(() => {
this.maybeStopPropagation(_event);
this.maybePreventDefault(_event);
const index = this.model.indexOf(option.id);
const isAtSelectionLimit =
this.settings.selectionLimit > 0 &&
this.model.length >= this.settings.selectionLimit;
const removeItem = (idx, id): void => {
this.model.splice(idx, 1);
this.removed.emit(id);
if (
this.settings.isLazyLoad &&
this.lazyLoadOptions.some(val => val.id === id)
) {
this.lazyLoadOptions.splice(
this.lazyLoadOptions.indexOf(
this.lazyLoadOptions.find(val => val.id === id)
),
1
);
}
};
if (index > -1) {
if (
this.settings.minSelectionLimit === undefined ||
this.numSelected > this.settings.minSelectionLimit
) {
removeItem(index, option.id);
}
const parentIndex =
option.parentId && this.model.indexOf(option.parentId);
if (parentIndex > -1) {
removeItem(parentIndex, option.parentId);
} else if (this.parents.indexOf(option.id) > -1) {
this.options
.filter(
child =>
this.model.indexOf(child.id) > -1 &&
child.parentId === option.id
)
.forEach(child =>
removeItem(this.model.indexOf(child.id), child.id)
);
}
} else if (isAtSelectionLimit && !this.settings.autoUnselect) {
this.selectionLimitReached.emit(this.model.length);
return;
} else {
const addItem = (id): void => {
this.model.push(id);
this.added.emit(id);
if (
this.settings.isLazyLoad &&
!this.lazyLoadOptions.some(val => val.id === id)
) {
this.lazyLoadOptions.push(option);
}
};
addItem(option.id);
if (!isAtSelectionLimit) {
if (option.parentId && !this.settings.ignoreLabels) {
const children = this.options.filter(
child =>
child.id !== option.id && child.parentId === option.parentId
);
if (children.every(child => this.model.indexOf(child.id) > -1)) {
addItem(option.parentId);
}
} else if (this.parents.indexOf(option.id) > -1) {
const children = this.options.filter(
child =>
this.model.indexOf(child.id) < 0 && child.parentId === option.id
);
children.forEach(child => addItem(child.id));
}
} else {
removeItem(0, this.model[0]);
}
}
if (this.settings.closeOnSelect) {
this.toggleDropdown();
}
this.model = this.model.slice();
this.fireModelChange();
}, 0)
}
updateNumSelected() {
this.numSelected =
this.model.filter(id => this.parents.indexOf(id) < 0).length || 0;
}
updateTitle() {
let numSelectedOptions = this.options.length;
if (this.settings.ignoreLabels) {
numSelectedOptions = this.options.filter(
(option: IMultiSelectOption) => !option.isLabel
).length;
}
if (this.numSelected === 0 || this.settings.fixedTitle) {
this.title = this.texts ? this.texts.defaultTitle : '';
} else if (
this.settings.displayAllSelectedText &&
this.model.length === numSelectedOptions
) {
this.title = this.texts ? this.texts.allSelected : '';
} else if (
this.settings.dynamicTitleMaxItems &&
this.settings.dynamicTitleMaxItems >= this.numSelected
) {
const useOptions =
this.settings.isLazyLoad && this.lazyLoadOptions.length
? this.lazyLoadOptions
: this.options;
let titleSelections: Array<IMultiSelectOption>;
if (this.settings.maintainSelectionOrderInTitle) {
const optionIds = useOptions.map((selectOption: IMultiSelectOption, idx: number) => selectOption.id);
titleSelections = this.model
.map((selectedId) => optionIds.indexOf(selectedId))
.filter((optionIndex) => optionIndex > -1)
.map((optionIndex) => useOptions[optionIndex]);
} else {
titleSelections = useOptions.filter((option: IMultiSelectOption) => this.model.indexOf(option.id) > -1);
}
this.title = titleSelections.map((option: IMultiSelectOption) => option.name).join(', ');
} else {
this.title =
this.numSelected +
' ' +
(this.numSelected === 1
? this.texts.checked
: this.texts.checkedPlural);
}
this.cdRef.markForCheck();
}
searchFilterApplied() {
return (
this.settings.enableSearch &&
this.filterControl.value &&
this.filterControl.value.length > 0
);
}
addChecks(options) {
const checkedOptions = options
.filter((option: IMultiSelectOption) => {
if (
!option.disabled &&
(
this.model.indexOf(option.id) === -1 &&
!(this.settings.ignoreLabels && option.isLabel)
)
) {
this.added.emit(option.id);
return true;
}
return false;
})
.map((option: IMultiSelectOption) => option.id);
this.model = this.model.concat(checkedOptions);
}
checkAll(): void {
if (!this.disabledSelection) {
this.addChecks(
!this.searchFilterApplied() ? this.options : this.filteredOptions
);
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
if (this.searchFilterApplied() && !this.checkAllStatus) {
this.checkAllSearchRegister.add(this.filterControl.value);
} else {
this.checkAllSearchRegister.clear();
this.checkAllStatus = true;
}
this.load();
}
this.fireModelChange();
}
}
uncheckAll(): void {
if (!this.disabledSelection) {
const checkedOptions = this.model;
let unCheckedOptions = !this.searchFilterApplied()
? this.model
: this.filteredOptions.map((option: IMultiSelectOption) => option.id);
// set unchecked options only to the ones that were checked
unCheckedOptions = checkedOptions.filter(item => unCheckedOptions.indexOf(item) > -1);
this.model = this.model.filter((id: number) => {
if (
(unCheckedOptions.indexOf(id) < 0 &&
this.settings.minSelectionLimit === undefined) ||
unCheckedOptions.indexOf(id) < this.settings.minSelectionLimit
) {
return true;
} else {
this.removed.emit(id);
return false;
}
});
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
if (this.searchFilterApplied()) {
if (this.checkAllSearchRegister.has(this.filterControl.value)) {
this.checkAllSearchRegister.delete(this.filterControl.value);
this.checkAllSearchRegister.forEach(function(searchTerm) {
const filterOptions = this.applyFilters(this.options.filter(option => unCheckedOptions.indexOf(option.id) > -1), searchTerm);
this.addChecks(filterOptions);
});
}
} else {
this.checkAllSearchRegister.clear();
this.checkAllStatus = false;
}
this.load();
}
this.fireModelChange();
}
}
preventCheckboxCheck(event: Event, option: IMultiSelectOption): void {
if (
option.disabled ||
(
this.settings.selectionLimit &&
!this.settings.autoUnselect &&
this.model.length >= this.settings.selectionLimit &&
this.model.indexOf(option.id) === -1 &&
this.maybePreventDefault(event)
)
) {
this.maybePreventDefault(event);
}
}
isCheckboxDisabled(option?: IMultiSelectOption): boolean {
return this.disabledSelection || option && option.disabled;
}
checkScrollPosition(ev): void {
const scrollTop = ev.target.scrollTop;
const scrollHeight = ev.target.scrollHeight;
const scrollElementHeight = ev.target.clientHeight;
const roundingPixel = 1;
const gutterPixel = 1;
if (
scrollTop >=
scrollHeight -
(1 + this.settings.loadViewDistance) * scrollElementHeight -
roundingPixel -
gutterPixel
) {
this.load();
}
}
checkScrollPropagation(ev, element): void {
const scrollTop = element.scrollTop;
const scrollHeight = element.scrollHeight;
const scrollElementHeight = element.clientHeight;
if (
(ev.deltaY > 0 && scrollTop + scrollElementHeight >= scrollHeight) ||
(ev.deltaY < 0 && scrollTop <= 0)
) {
ev = ev || window.event;
this.maybePreventDefault(ev);
ev.returnValue = false;
}
}
trackById(idx: number, selectOption: IMultiSelectOption): void {
return selectOption.id;
}
load(): void {
this.lazyLoad.emit({
length: this.options.length,
filter: this.filterControl.value,
checkAllSearches: this.checkAllSearchRegister,
checkAllStatus: this.checkAllStatus,
});
}
focusItem(dir: number, e?: Event): void {
if (!this.isVisible) {
return;
}
this.maybePreventDefault(e);
const idx = this.filteredOptions.indexOf(this.focusedItem);
if (idx === -1) {
this.focusedItem = this.filteredOptions[0];
return;
}
const nextIdx = idx + dir;
const newIdx =
nextIdx < 0
? this.filteredOptions.length - 1
: nextIdx % this.filteredOptions.length;
this.focusedItem = this.filteredOptions[newIdx];
}
private maybePreventDefault(e?: Event): void {
if (e && e.preventDefault) {
e.preventDefault();
}
}
private maybeStopPropagation(e?: Event): void {
if (e && e.stopPropagation) {
e.stopPropagation();
}
}
private _escapeRegExp(str: string): RegExp {
const regExpStr = str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
return new RegExp(regExpStr, 'i');
}
}

View File

@ -0,0 +1,39 @@
import { Directive, HostListener } from '@angular/core';
import { EventEmitter } from '@angular/core';
import { Output } from '@angular/core';
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[offClick]',
})
export class OffClickDirective {
@Output('offClick') onOffClick = new EventEmitter<any>();
private _clickEvent: MouseEvent;
private _touchEvent: TouchEvent;
@HostListener('click', ['$event'])
public onClick(event: MouseEvent): void {
this._clickEvent = event;
}
@HostListener('touchstart', ['$event'])
public onTouch(event: TouchEvent): void {
this._touchEvent = event;
}
@HostListener('document:click', ['$event'])
public onDocumentClick(event: MouseEvent): void {
if (event !== this._clickEvent) {
this.onOffClick.emit(event);
}
}
@HostListener('document:touchstart', ['$event'])
public onDocumentTouch(event: TouchEvent): void {
if (event !== this._touchEvent) {
this.onOffClick.emit(event);
}
}
}

View File

@ -0,0 +1,130 @@
import { Pipe, PipeTransform } from '@angular/core';
import { IMultiSelectOption } from './types';
interface StringHashMap<T> {
[k: string]: T;
}
@Pipe({
name: 'searchFilter'
})
export class MultiSelectSearchFilter implements PipeTransform {
private _lastOptions: IMultiSelectOption[];
private _searchCache: StringHashMap<IMultiSelectOption[]> = {};
private _searchCacheInclusive: StringHashMap<boolean | number> = {};
private _prevSkippedItems: StringHashMap<number> = {};
transform(
options: IMultiSelectOption[],
str = '',
limit = 0,
renderLimit = 0,
searchFunction: (str: string) => RegExp,
): IMultiSelectOption[] {
str = str.toLowerCase();
// Drop cache because options were updated
if (options !== this._lastOptions) {
this._lastOptions = options;
this._searchCache = {};
this._searchCacheInclusive = {};
this._prevSkippedItems = {};
}
const filteredOpts = this._searchCache.hasOwnProperty(str)
? this._searchCache[str]
: this._doSearch(options, str, limit, searchFunction);
const isUnderLimit = options.length <= limit;
return isUnderLimit
? filteredOpts
: this._limitRenderedItems(filteredOpts, renderLimit);
}
private _getSubsetOptions(
options: IMultiSelectOption[],
prevOptions: IMultiSelectOption[],
prevSearchStr: string
) {
const prevInclusiveOrIdx = this._searchCacheInclusive[prevSearchStr];
if (prevInclusiveOrIdx === true) {
// If have previous results and it was inclusive, do only subsearch
return prevOptions;
} else if (typeof prevInclusiveOrIdx === 'number') {
// Or reuse prev results with unchecked ones
return [...prevOptions, ...options.slice(prevInclusiveOrIdx)];
}
return options;
}
private _doSearch(options: IMultiSelectOption[], str: string, limit: number, searchFunction: (str: string) => RegExp) {
const prevStr = str.slice(0, -1);
const prevResults = this._searchCache[prevStr];
const prevResultShift = this._prevSkippedItems[prevStr] || 0;
if (prevResults) {
options = this._getSubsetOptions(options, prevResults, prevStr);
}
const optsLength = options.length;
const maxFound = limit > 0 ? Math.min(limit, optsLength) : optsLength;
const regexp = searchFunction(str);
const filteredOpts: IMultiSelectOption[] = [];
let i = 0, founded = 0, removedFromPrevResult = 0;
const doesOptionMatch = (option: IMultiSelectOption) => regexp.test(option.name);
const getChildren = (option: IMultiSelectOption) =>
options.filter(child => child.parentId === option.id);
const getParent = (option: IMultiSelectOption) =>
options.find(parent => option.parentId === parent.id);
const foundFn = (item: any) => { filteredOpts.push(item); founded++; };
const notFoundFn = prevResults ? () => removedFromPrevResult++ : () => { };
for (; i < optsLength && founded < maxFound; ++i) {
const option = options[i];
const directMatch = doesOptionMatch(option);
if (directMatch) {
foundFn(option);
continue;
}
if (typeof option.parentId === 'undefined') {
const childrenMatch = getChildren(option).some(doesOptionMatch);
if (childrenMatch) {
foundFn(option);
continue;
}
}
if (typeof option.parentId !== 'undefined') {
const parentMatch = doesOptionMatch(getParent(option));
if (parentMatch) {
foundFn(option);
continue;
}
}
notFoundFn();
}
const totalIterations = i + prevResultShift;
this._searchCache[str] = filteredOpts;
this._searchCacheInclusive[str] = i === optsLength || totalIterations;
this._prevSkippedItems[str] = removedFromPrevResult + prevResultShift;
return filteredOpts;
}
private _limitRenderedItems<T>(items: T[], limit: number): T[] {
return items.length > limit && limit > 0 ? items.slice(0, limit) : items;
}
}

View File

@ -0,0 +1,82 @@
export interface IMultiSelectOption {
id: any;
name: string;
disabled?: boolean;
isLabel?: boolean;
parentId?: any;
params?: any;
classes?: string;
image?: string;
}
export interface IMultiSelectSettings {
pullRight?: boolean;
enableSearch?: boolean;
closeOnClickOutside?: boolean;
/**
* 0 - By default
* If `enableSearch=true` and total amount of items more then `searchRenderLimit` (0 - No limit)
* then render items only when user typed more then or equal `searchRenderAfter` charachters
*/
searchRenderLimit?: number;
/**
* 3 - By default
*/
searchRenderAfter?: number;
/**
* 0 - By default
* If >0 will render only N first items
*/
searchMaxLimit?: number;
/**
* 0 - By default
* Used with searchMaxLimit to further limit rendering for optimization
* Should be less than searchMaxLimit to take effect
*/
searchMaxRenderedItems?: number;
checkedStyle?: 'checkboxes' | 'glyphicon' | 'fontawesome' | 'visual';
buttonClasses?: string;
itemClasses?: string;
containerClasses?: string;
selectionLimit?: number;
minSelectionLimit?: number;
closeOnSelect?: boolean;
autoUnselect?: boolean;
showCheckAll?: boolean;
showUncheckAll?: boolean;
fixedTitle?: boolean;
dynamicTitleMaxItems?: number;
maxHeight?: string;
displayAllSelectedText?: boolean;
isLazyLoad?: boolean;
loadViewDistance?: number;
stopScrollPropagation?: boolean;
selectAddedValues?: boolean;
/**
* false - By default
* If activated label IDs don't count and won't be written to the model.
*/
ignoreLabels?: boolean;
/**
* false - By default
* If activated, the title will show selections in the order they were selected.
*/
maintainSelectionOrderInTitle?: boolean;
/**
* @default true
* Set the focus back to the input control when the dropdown closed
*/
focusBack?: boolean;
}
export interface IMultiSelectTexts {
checkAll?: string;
uncheckAll?: string;
checked?: string;
checkedPlural?: string;
searchPlaceholder?: string;
searchEmptyResult?: string;
searchNoRenderText?: string;
defaultTitle?: string;
allSelected?: string;
}

View File

@ -1295,18 +1295,6 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Mainnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-mainnet" "${BITCOIN_ELECTRS_HOME}"
echo "[*] Installing Bitcoin crontab"
case $OS in
FreeBSD)
echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
;;
Debian)
(crontab -l ; echo "@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Mainnet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
@ -1321,13 +1309,6 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Testnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-testnet" "${BITCOIN_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Bitcoin-testnet crontab"
(crontab -l ; echo "@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Testnet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
@ -1342,13 +1323,6 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Signet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-signet" "${BITCOIN_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Bitcoin-signet crontab"
(crontab -l ; echo "@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Signet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
@ -1369,9 +1343,6 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
;;
Debian)
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
;;
esac
echo "[*] Configuring Elements Liquid RPC credentials in electrs start script"
@ -1388,13 +1359,6 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
echo "[*] Installing Elements Liquid Testnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-liquidtestnet" "${ELEMENTS_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Elements-testnet crontab"
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
;;
esac
echo "[*] Installing Elements Liquid Testnet RPC credentials"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
@ -1407,6 +1371,45 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
fi
################################
# Install all Electrs Cronjobs #
################################
echo "[*] Installing crontabs"
case $OS in
FreeBSD)
echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
;;
Debian)
crontab_bitcoin=()
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
echo [*] Installing Electrs Mainnet Cronjob
crontab_bitcoin+="@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet\n"
fi
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
echo [*] Installing Electrs Testnet Cronjob
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet\n"
fi
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
echo [*] Installing Electrs Signet Cronjob
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet\n"
fi
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
crontab_elements=()
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
echo [*] Installing Liquid Asset Mainnet Cronjob
crontab_elements+="6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1\n"
fi
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
echo [*] Installing Liquid Asset Testnet Cronjob
crontab_elements+="6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1\n"
fi
echo "${crontab_elements}" | crontab -u "${ELEMENTS_USER}" -
;;
esac
#####################################
# Bisq instance for Bitcoin Mainnet #
#####################################
@ -1553,6 +1556,29 @@ case $OS in
;;
esac
##### OS set Linux user ulimits
echo "[*] Setting ulimits for users"
case $OS in
FreeBSD)
;;
Debian)
cat >> /etc/security/limits.conf <<EOF
* soft nproc 200000
* hard nproc 200000
* soft nofile 200000
* hard nofile 200000
EOF
echo "session required pam_limits.so" >> /etc/pam.d/common-session
;;
esac
##### OS services
#if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
@ -1628,6 +1654,8 @@ esac
##### finish
echo 'Please reboot to start all the services.'
echo '[*] Done!'
exit 0

View File

@ -56,7 +56,7 @@ build_frontend()
if [ ! -e "mempool-frontend-config.json" ];then
cp "${HOME}/mempool/production/mempool-frontend-config.${site}.json" "mempool-frontend-config.json"
fi
npm install --prod --no-optional || exit 1
npm install --omit=dev --omit=optional || exit 1
npm run build || exit 1
}
@ -75,7 +75,7 @@ build_backend()
-e "s!__ELEMENTS_RPC_PASS__!${ELEMENTS_RPC_PASS}!" \
"mempool-config.json"
fi
npm install --prod --no-optional || exit 1
npm install --omit=dev --omit=optional || exit 1
npm run build || exit 1
}