mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
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:
committed by
Aiden McClelland
parent
673e5af030
commit
06cf83b901
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
63
frontend/projects/ui/src/app/services/time-service.ts
Normal file
63
frontend/projects/ui/src/app/services/time-service.ts
Normal 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,
|
||||
) {}
|
||||
}
|
||||
Reference in New Issue
Block a user