WIP: IP, pubkey, system time, system uptime, ca fingerprint (#2091)

* closes #923, #2063, #2012, #1153

* add ca fingerprint

* add `server.time`

* add `ip-info` to `server-info`

* add ssh pubkey

* support multiple IPs

* rename key

* add `ca-fingerprint` and `system-start-time`

* fix off-by-one

* update compat cargo lock

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2023-01-16 14:54:35 -07:00
committed by Aiden McClelland
parent 673e5af030
commit 06cf83b901
40 changed files with 2244 additions and 925 deletions

View File

@@ -15,6 +15,32 @@
<div id="metricSection">
<ng-container *ngIf="!loading">
<ion-item-group>
<ion-item-divider>Time</ion-item-divider>
<ion-item>
<ion-label>System Time</ion-label>
<ion-note slot="end" class="metric-note">
<ion-text style="color: white"
>{{ systemTime$ | async | date:'MMMM d, y, h:mm a z':'UTC'
}}</ion-text
>
</ion-note>
</ion-item>
<ion-item>
<ion-label>System Uptime</ion-label>
<ion-note
*ngIf="systemUptime$ | async as uptime"
slot="end"
class="metric-note"
>
<ion-text style="color: white">
<b>{{ uptime.days }}</b> Days, <b>{{ uptime.hours }}</b> Hours,
<b>{{ uptime.minutes }}</b> Minutes
</ion-text>
</ion-note>
</ion-item>
</ion-item-group>
<ion-item-group
*ngFor="let metricGroup of metrics | keyvalue : asIsOrder"
>

View File

@@ -1,6 +1,7 @@
import { Component } from '@angular/core'
import { Metrics } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TimeService } from 'src/app/services/time-service'
import { pauseFor, ErrorToastService } from '@start9labs/shared'
@Component({
@@ -13,9 +14,13 @@ export class ServerMetricsPage {
going = false
metrics: Metrics = {}
readonly systemTime$ = this.timeService.systemTime$
readonly systemUptime$ = this.timeService.systemUptime$
constructor(
private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService,
private readonly timeService: TimeService,
) {}
async ngOnInit() {

View File

@@ -8,28 +8,28 @@
</ion-header>
<ion-content>
<ion-item-divider>Basic</ion-item-divider>
<ion-item-group *ngIf="server$ | async as server">
<ion-item-divider>embassyOS Info</ion-item-divider>
<ion-item>
<ion-label>
<h2>embassyOS Version</h2>
<h2>Version</h2>
<p>{{ server.version | displayEmver }}</p>
</ion-label>
</ion-item>
<ion-item>
<ion-label>
<h2>Git Hash</h2>
<p>{{ gitHash }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy(gitHash)">
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
<ion-item-divider>Addresses</ion-item-divider>
<ion-item-divider>Web Addresses</ion-item-divider>
<ion-item>
<ion-label class="break-all">
<h2>Tor Address</h2>
<h2>Tor</h2>
<p>{{ server['tor-address'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy(server['tor-address'])">
@@ -38,12 +38,49 @@
</ion-item>
<ion-item>
<ion-label class="break-all">
<h2>LAN Address</h2>
<h2>LAN</h2>
<p>{{ server['lan-address'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy(server['lan-address'])">
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
<ng-container *ngFor="let ip of server['ip-info'] | keyvalue">
<ng-container *ngFor="let entry of ip.value | keyvalue">
<ion-item *ngIf="entry.value as address">
<ion-label>
<h2>{{ ip.key }} ({{ entry.key }})</h2>
<p>{{ address }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy(address)">
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
</ng-container>
</ng-container>
<ion-item-divider>Device Credentials</ion-item-divider>
<ion-item>
<ion-label>
<h2>Pubkey</h2>
<p>{{ server['pubkey'] }}</p>
</ion-label>
<ion-button slot="end" fill="clear" (click)="copy(server['pubkey'])">
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
<ion-item>
<ion-label>
<h2>CA fingerprint</h2>
<p>{{ server['ca-fingerprint'] }}</p>
</ion-label>
<ion-button
slot="end"
fill="clear"
(click)="copy(server['ca-fingerprint'])"
>
<ion-icon slot="icon-only" name="copy-outline"></ion-icon>
</ion-button>
</ion-item>
</ion-item-group>
</ion-content>

View File

@@ -35,6 +35,9 @@ export module RR {
export type EchoReq = { message: string } // server.echo
export type EchoRes = string
export type GetSystemTimeReq = {} // server.time
export type GetSystemTimeRes = string
export type GetServerLogsReq = ServerLogsReq // server.logs & server.kernel-logs
export type GetServerLogsRes = LogsRes

View File

@@ -57,6 +57,10 @@ export abstract class ApiService {
config: WebSocketSubjectConfig<Log>,
): Observable<Log>
abstract getSystemTime(
params: RR.GetSystemTimeReq,
): Promise<RR.GetSystemTimeRes>
abstract getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes>

View File

@@ -117,6 +117,12 @@ export class LiveApiService extends ApiService {
return this.openWebsocket(config)
}
async getSystemTime(
params: RR.GetSystemTimeReq,
): Promise<RR.GetSystemTimeRes> {
return this.rpcRequest({ method: 'server.time', params })
}
async getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {

View File

@@ -177,6 +177,13 @@ export class MockApiService extends ApiService {
)
}
async getSystemTime(
params: RR.GetSystemTimeReq,
): Promise<RR.GetSystemTimeRes> {
await pauseFor(2000)
return new Date().toUTCString()
}
async getServerLogs(
params: RR.GetServerLogsReq,
): Promise<RR.GetServerLogsRes> {

View File

@@ -39,6 +39,16 @@ export const mockPatchData: DataModel = {
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
'lan-address': 'https://embassy-abcdefgh.local',
'tor-address': 'http://myveryownspecialtoraddress.onion',
'ip-info': {
eth0: {
ipv4: '10.0.0.1',
ipv6: null,
},
wlan0: {
ipv4: '10.0.90.12',
ipv6: 'FE80:CD00:0000:0CDE:1257:0000:211E:729CD',
},
},
'last-wifi-region': null,
'unread-notification-count': 4,
// password is asdfasdf
@@ -51,6 +61,9 @@ export const mockPatchData: DataModel = {
'update-progress': null,
},
hostname: 'random-words',
pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
'ca-fingerprint': 'SHA-256: 63 2B 11 99 44 40 17 DF 37 FC C3 DF 0F 3D 15',
'system-start-time': new Date(new Date().valueOf() - 360042).toUTCString(),
},
'package-data': {
bitcoind: {

View File

@@ -52,12 +52,23 @@ export interface ServerInfo {
'last-backup': string | null
'lan-address': Url
'tor-address': Url
'ip-info': IpInfo
'last-wifi-region': string | null
'unread-notification-count': number
'status-info': ServerStatusInfo
'eos-version-compat': string
'password-hash': string
hostname: string
pubkey: string
'ca-fingerprint': string
'system-start-time': string
}
export interface IpInfo {
[iface: string]: {
ipv4: string | null
ipv6: string | null
}
}
export interface ServerStatusInfo {

View File

@@ -0,0 +1,63 @@
import { Injectable } from '@angular/core'
import {
map,
shareReplay,
startWith,
switchMap,
take,
tap,
} from 'rxjs/operators'
import { PatchDB } from 'patch-db-client'
import { DataModel } from './patch-db/data-model'
import { ApiService } from './api/embassy-api.service'
import { combineLatest, from, timer } from 'rxjs'
@Injectable({
providedIn: 'root',
})
export class TimeService {
private readonly startTimeMs$ = this.patch
.watch$('server-info', 'system-start-time')
.pipe(
take(1),
map(startTime => new Date(startTime).valueOf()),
shareReplay(),
)
readonly systemTime$ = from(this.apiService.getSystemTime({})).pipe(
switchMap(utcStr => {
const dateObj = new Date(utcStr)
const msRemaining = (60 - dateObj.getSeconds()) * 1000
dateObj.setSeconds(0)
const current = dateObj.valueOf()
return timer(msRemaining, 60000).pipe(
map(index => {
const incremented = index + 1
const msToAdd = 60000 * incremented
return current + msToAdd
}),
startWith(current),
)
}),
)
readonly systemUptime$ = combineLatest([
this.startTimeMs$,
this.systemTime$,
]).pipe(
map(([startTime, currentTime]) => {
const ms = currentTime - startTime
const days = Math.floor(ms / (24 * 60 * 60 * 1000))
const daysms = ms % (24 * 60 * 60 * 1000)
const hours = Math.floor(daysms / (60 * 60 * 1000))
const hoursms = ms % (60 * 60 * 1000)
const minutes = Math.floor(hoursms / (60 * 1000))
return { days, hours, minutes }
}),
)
constructor(
private readonly patch: PatchDB<DataModel>,
private readonly apiService: ApiService,
) {}
}