Merge branch 'master' into simon/search-results-greyed
This commit is contained in:
		
						commit
						38324575e8
					
				@ -257,7 +257,8 @@ class ChannelsApi {
 | 
			
		||||
      let query = `
 | 
			
		||||
        SELECT COALESCE(node2.alias, SUBSTRING(node2_public_key, 0, 20)) AS alias, COALESCE(node2.public_key, node2_public_key) AS public_key,
 | 
			
		||||
          channels.status, channels.node1_fee_rate,
 | 
			
		||||
          channels.capacity, channels.short_id, channels.id, channels.closing_reason
 | 
			
		||||
          channels.capacity, channels.short_id, channels.id, channels.closing_reason,
 | 
			
		||||
          UNIX_TIMESTAMP(closing_date) as closing_date, UNIX_TIMESTAMP(channels.updated_at) as updated_at
 | 
			
		||||
        FROM channels
 | 
			
		||||
        LEFT JOIN nodes AS node2 ON node2.public_key = channels.node2_public_key
 | 
			
		||||
        WHERE node1_public_key = ? AND channels.status ${channelStatusFilter}
 | 
			
		||||
@ -268,7 +269,8 @@ class ChannelsApi {
 | 
			
		||||
      query = `
 | 
			
		||||
        SELECT COALESCE(node1.alias, SUBSTRING(node1_public_key, 0, 20)) AS alias, COALESCE(node1.public_key, node1_public_key) AS public_key,
 | 
			
		||||
          channels.status, channels.node2_fee_rate,
 | 
			
		||||
          channels.capacity, channels.short_id, channels.id, channels.closing_reason
 | 
			
		||||
          channels.capacity, channels.short_id, channels.id, channels.closing_reason,
 | 
			
		||||
          UNIX_TIMESTAMP(closing_date) as closing_date, UNIX_TIMESTAMP(channels.updated_at) as updated_at
 | 
			
		||||
        FROM channels
 | 
			
		||||
        LEFT JOIN nodes AS node1 ON node1.public_key = channels.node1_public_key
 | 
			
		||||
        WHERE node2_public_key = ? AND channels.status ${channelStatusFilter}
 | 
			
		||||
@ -277,7 +279,15 @@ class ChannelsApi {
 | 
			
		||||
 | 
			
		||||
      let allChannels = channelsFromNode.concat(channelsToNode);
 | 
			
		||||
      allChannels.sort((a, b) => {
 | 
			
		||||
        return b.capacity - a.capacity;
 | 
			
		||||
        if (status === 'closed') {
 | 
			
		||||
          if (!b.closing_date && !a.closing_date) {
 | 
			
		||||
            return (b.updated_at ?? 0) - (a.updated_at ?? 0);
 | 
			
		||||
          } else {
 | 
			
		||||
            return (b.closing_date ?? 0) - (a.closing_date ?? 0);
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          return b.capacity - a.capacity;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      if (index >= 0) {
 | 
			
		||||
@ -294,6 +304,7 @@ class ChannelsApi {
 | 
			
		||||
          channel = {
 | 
			
		||||
            status: row.status,
 | 
			
		||||
            closing_reason: row.closing_reason,
 | 
			
		||||
            closing_date: row.closing_date,
 | 
			
		||||
            capacity: row.capacity ?? 0,
 | 
			
		||||
            short_id: row.short_id,
 | 
			
		||||
            id: row.id,
 | 
			
		||||
@ -522,6 +533,23 @@ class ChannelsApi {
 | 
			
		||||
      logger.err('$setChannelsInactive() error: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getLatestChannelUpdateForNode(publicKey: string): Promise<number> {
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `
 | 
			
		||||
        SELECT MAX(UNIX_TIMESTAMP(updated_at)) as updated_at
 | 
			
		||||
        FROM channels
 | 
			
		||||
        WHERE node1_public_key = ?
 | 
			
		||||
      `;
 | 
			
		||||
      const [rows]: any[] = await DB.query(query, [publicKey]);
 | 
			
		||||
      if (rows.length > 0) {
 | 
			
		||||
        return rows[0].updated_at;
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err(`Can't getLatestChannelUpdateForNode for ${publicKey}. Reason ${e instanceof Error ? e.message : e}`);
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new ChannelsApi();
 | 
			
		||||
 | 
			
		||||
@ -63,6 +63,9 @@ class NetworkSyncService {
 | 
			
		||||
    let deletedSockets = 0;
 | 
			
		||||
    const graphNodesPubkeys: string[] = [];
 | 
			
		||||
    for (const node of nodes) {
 | 
			
		||||
      const latestUpdated = await channelsApi.$getLatestChannelUpdateForNode(node.pub_key);
 | 
			
		||||
      node.last_update = Math.max(node.last_update, latestUpdated);
 | 
			
		||||
 | 
			
		||||
      await nodesApi.$saveNode(node);
 | 
			
		||||
      graphNodesPubkeys.push(node.pub_key);
 | 
			
		||||
      ++progress;
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,13 @@
 | 
			
		||||
    
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
      <button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary"><fa-icon [icon]="['fas', 'search']" [fixedWidth]="true" i18n-title="search-form.search-title" title="Search"></fa-icon></button>
 | 
			
		||||
      <button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary">
 | 
			
		||||
        <fa-icon *ngIf="!(isTypeaheading$ | async) else searchLoading" [icon]="['fas', 'search']" [fixedWidth]="true" i18n-title="search-form.search-title" title="Search"></fa-icon>
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</form>
 | 
			
		||||
 | 
			
		||||
<ng-template #searchLoading>
 | 
			
		||||
  <div class="spinner-border spinner-border-sm text-light" role="status" aria-hidden="true" (click)="searchForm.valid && search()"></div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -49,4 +49,10 @@ form {
 | 
			
		||||
  .btn {
 | 
			
		||||
    width: 100px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner-border {
 | 
			
		||||
  vertical-align: text-top;
 | 
			
		||||
  margin-top: 1px;
 | 
			
		||||
  margin-right: 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,8 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
import { Observable, of, Subject, merge, zip } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged, switchMap, filter, catchError, map } from 'rxjs/operators';
 | 
			
		||||
import { Observable, of, Subject, zip, BehaviorSubject } from 'rxjs';
 | 
			
		||||
import { debounceTime, distinctUntilChanged, switchMap, catchError, map } from 'rxjs/operators';
 | 
			
		||||
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
 | 
			
		||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
@ -20,13 +20,14 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
  network = '';
 | 
			
		||||
  assets: object = {};
 | 
			
		||||
  isSearching = false;
 | 
			
		||||
  isTypeaheading$ = new BehaviorSubject<boolean>(false);
 | 
			
		||||
  typeAhead$: Observable<any>;
 | 
			
		||||
  searchForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100})$/;
 | 
			
		||||
  regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
 | 
			
		||||
  regexTransaction = /^([a-fA-F0-9]{64}):?(\d+)?$/;
 | 
			
		||||
  regexBlockheight = /^[0-9]+$/;
 | 
			
		||||
  regexTransaction = /^([a-fA-F0-9]{64})(:\d+)?$/;
 | 
			
		||||
  regexBlockheight = /^[0-9]{1,9}$/;
 | 
			
		||||
  focus$ = new Subject<string>();
 | 
			
		||||
  click$ = new Subject<string>();
 | 
			
		||||
 | 
			
		||||
@ -68,7 +69,7 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
          }
 | 
			
		||||
          return text.trim();
 | 
			
		||||
        }),
 | 
			
		||||
        debounceTime(250),
 | 
			
		||||
        debounceTime(200),
 | 
			
		||||
        distinctUntilChanged(),
 | 
			
		||||
        switchMap((text) => {
 | 
			
		||||
          if (!text.length) {
 | 
			
		||||
@ -80,6 +81,7 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
              }
 | 
			
		||||
            ]);
 | 
			
		||||
          }
 | 
			
		||||
          this.isTypeaheading$.next(true);
 | 
			
		||||
          if (!this.stateService.env.LIGHTNING) {
 | 
			
		||||
            return zip(
 | 
			
		||||
              this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
 | 
			
		||||
@ -95,6 +97,7 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
          );
 | 
			
		||||
        }),
 | 
			
		||||
        map((result: any[]) => {
 | 
			
		||||
          this.isTypeaheading$.next(false);
 | 
			
		||||
          if (this.network === 'bisq') {
 | 
			
		||||
            return result[0].map((address: string) => 'B' + address);
 | 
			
		||||
          }
 | 
			
		||||
@ -153,6 +156,7 @@ export class SearchFormComponent implements OnInit {
 | 
			
		||||
          this.navigate('/tx/', matches[0]);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        this.searchResults.searchButtonClick();
 | 
			
		||||
        this.isSearching = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,13 @@ export class SearchResultsComponent implements OnChanges {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  searchButtonClick() {
 | 
			
		||||
    if (this.resultsFlattened[this.activeIdx]) {
 | 
			
		||||
      this.selectedResult.emit(this.resultsFlattened[this.activeIdx]);
 | 
			
		||||
      this.results = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleKeyDown(event: KeyboardEvent) {
 | 
			
		||||
    switch (event.key) {
 | 
			
		||||
      case 'ArrowDown':
 | 
			
		||||
 | 
			
		||||
@ -65,14 +65,18 @@
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngIf="transactions$ | async as transactions">
 | 
			
		||||
      <ng-template [ngIf]="transactions[0]">
 | 
			
		||||
        <h3>Opening transaction</h3>
 | 
			
		||||
        <app-transactions-list [transactions]="[transactions[0]]" [showConfirmations]="true" [rowLimit]="5"></app-transactions-list>
 | 
			
		||||
        <div class="d-flex">
 | 
			
		||||
          <h3>Opening transaction</h3>
 | 
			
		||||
          <button type="button" class="btn btn-outline-info details-button btn-sm" (click)="txList1.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <app-transactions-list #txList1 [transactions]="[transactions[0]]" [showConfirmations]="true" [rowLimit]="5"></app-transactions-list>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
      <ng-template [ngIf]="transactions[1]">
 | 
			
		||||
        <div class="closing-header">
 | 
			
		||||
        <div class="closing-header d-flex">
 | 
			
		||||
          <h3 style="margin: 0;">Closing transaction</h3>  <app-closing-type [type]="channel.closing_reason"></app-closing-type>
 | 
			
		||||
          <button type="button" class="btn btn-outline-info details-button btn-sm" (click)="txList2.toggleDetails()" i18n="transaction.details|Transaction Details">Details</button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <app-transactions-list [transactions]="[transactions[1]]" [showConfirmations]="true" [rowLimit]="5"></app-transactions-list>
 | 
			
		||||
        <app-transactions-list #txList2 [transactions]="[transactions[1]]" [showConfirmations]="true" [rowLimit]="5"></app-transactions-list>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -45,19 +45,29 @@ app-fiat {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.closing-header {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  margin-bottom: 1rem;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
h3 {
 | 
			
		||||
  margin-bottom: 0rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 768px) {
 | 
			
		||||
  h3 {
 | 
			
		||||
    font-size: 1.4rem;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
  }
 | 
			
		||||
  .closing-header {
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
  }
 | 
			
		||||
  app-closing-type {
 | 
			
		||||
    flex-basis: 100%;
 | 
			
		||||
    order: 1;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.loading-spinner {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 400px;
 | 
			
		||||
@ -67,4 +77,9 @@ app-fiat {
 | 
			
		||||
  @media (max-width: 767.98px) {
 | 
			
		||||
    top: 450px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.details-button {
 | 
			
		||||
  align-self: center;
 | 
			
		||||
  margin-left: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -22,30 +22,30 @@
 | 
			
		||||
 | 
			
		||||
    <div class="row">
 | 
			
		||||
      <div class="col-md">
 | 
			
		||||
        <table class="table table-borderless table-striped">
 | 
			
		||||
        <table class="table table-borderless table-striped table-fixed">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="lightning.active-capacity">Active capacity</td>
 | 
			
		||||
              <td i18n="lightning.active-capacity" class="text-truncate label">Active capacity</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <app-sats [satoshis]="node.capacity"></app-sats>
 | 
			
		||||
                <app-fiat [value]="node.capacity" digitsInfo="1.0-0"></app-fiat>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="lightning.active-channels">Active channels</td>
 | 
			
		||||
              <td i18n="lightning.active-channels" class="text-truncate label">Active channels</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                {{ node.active_channel_count }}
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="lightning.active-channels-avg">Average channel size</td>
 | 
			
		||||
              <td i18n="lightning.active-channels-avg" class="text-wrap label">Average channel size</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <app-sats [satoshis]="node.avgCapacity"></app-sats>
 | 
			
		||||
                <app-fiat [value]="node.avgCapacity" digitsInfo="1.0-0"></app-fiat>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr *ngIf="node.geolocation">
 | 
			
		||||
              <td i18n="location">Location</td>
 | 
			
		||||
              <td i18n="location" class="text-truncate">Location</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <app-geolocation [data]="node.geolocation" [type]="'node'"></app-geolocation>
 | 
			
		||||
              </td>
 | 
			
		||||
@ -55,30 +55,30 @@
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="w-100 d-block d-md-none"></div>
 | 
			
		||||
      <div class="col-md">
 | 
			
		||||
        <table class="table table-borderless table-striped">
 | 
			
		||||
        <table class="table table-borderless table-striped table-fixed">
 | 
			
		||||
          <tbody>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="address.total-received">First seen</td>
 | 
			
		||||
              <td i18n="address.total-received" class="text-truncate label">First seen</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <app-timestamp [unixTime]="node.first_seen"></app-timestamp>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="address.total-sent">Last update</td>
 | 
			
		||||
              <td i18n="address.total-sent" class="text-truncate label">Last update</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <app-timestamp [unixTime]="node.updated_at"></app-timestamp>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr>
 | 
			
		||||
              <td i18n="address.balance">Color</td>
 | 
			
		||||
              <td i18n="address.balance" class="text-truncate label">Color</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <div [ngStyle]="{'color': node.color}">{{ node.color }}</div>
 | 
			
		||||
              </td>
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr *ngIf="node.country">
 | 
			
		||||
              <td i18n="isp">ISP</td>
 | 
			
		||||
              <td i18n="isp" class="text-truncate label">ISP</td>
 | 
			
		||||
              <td>
 | 
			
		||||
                <a [routerLink]="['/lightning/nodes/isp' | relativeUrl, node.as_number]">
 | 
			
		||||
                <a class="d-block text-wrap" [routerLink]="['/lightning/nodes/isp' | relativeUrl, node.as_number]">
 | 
			
		||||
                  {{ node.as_organization }} [ASN {{node.as_number}}]
 | 
			
		||||
                </a>                
 | 
			
		||||
              </td>
 | 
			
		||||
 | 
			
		||||
@ -77,3 +77,10 @@ app-fiat {
 | 
			
		||||
  left: calc(50% - 15px);
 | 
			
		||||
  z-index: 100;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.label {
 | 
			
		||||
  width: 50%;
 | 
			
		||||
  @media (min-width: 576px) {
 | 
			
		||||
    width: 40%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
<span [innerHTML]="formattedLocation"></span>
 | 
			
		||||
<span class="d-block text-truncate" [innerHTML]="formattedLocation"></span>
 | 
			
		||||
@ -99,20 +99,15 @@ class Server {
 | 
			
		||||
          throw new Error('failed to access open graph service');
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // wait for navigation to complete
 | 
			
		||||
      await page.waitForFunction(
 | 
			
		||||
        (path) => window.location.pathname.includes(path),
 | 
			
		||||
        {},
 | 
			
		||||
        path
 | 
			
		||||
      );
 | 
			
		||||
      // wait for preview component to initialize
 | 
			
		||||
      await page.waitForSelector('meta[property="og:preview:loading"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 })
 | 
			
		||||
 | 
			
		||||
      const waitForReady = await page.$('meta[property="og:preview:loading"]');
 | 
			
		||||
      let success = true;
 | 
			
		||||
      success = await Promise.race([
 | 
			
		||||
        page.waitForSelector('meta[property="og:preview:ready"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 }).then(() => true),
 | 
			
		||||
        page.waitForSelector('meta[property="og:preview:fail"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 }).then(() => false)
 | 
			
		||||
      ])
 | 
			
		||||
      if (waitForReady != null) {
 | 
			
		||||
        success = await Promise.race([
 | 
			
		||||
          page.waitForSelector('meta[property="og:preview:ready"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 }).then(() => true),
 | 
			
		||||
          page.waitForSelector('meta[property="og:preview:fail"]', { timeout: config.PUPPETEER.RENDER_TIMEOUT || 3000 }).then(() => false)
 | 
			
		||||
        ])
 | 
			
		||||
      }
 | 
			
		||||
      if (success) {
 | 
			
		||||
        const screenshot = await page.screenshot();
 | 
			
		||||
        return screenshot;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user