rework ca-wiz and add icons to menu for warnings (#2486)

* rework ca-wiz and add icons to menu for warnings

* remove root CA button from home page

* load fonts before calling complete in setup wiz
This commit is contained in:
Matt Hill
2023-11-01 13:36:56 -06:00
committed by GitHub
parent c14ca1d7fd
commit 1dad7965d2
10 changed files with 172 additions and 205 deletions

View File

@@ -45,18 +45,7 @@ export class SuccessPage {
async ngAfterViewInit() { async ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => this.initMatrix()) this.ngZone.runOutsideAngular(() => this.initMatrix())
try { setTimeout(() => this.complete(), 1000)
const ret = await this.api.complete()
if (!this.isKiosk) {
this.torAddress = ret['tor-address']
this.lanAddress = ret['lan-address'].replace(/^https:/, 'http:')
this.cert = ret['root-ca']
await this.api.exit()
}
} catch (e: any) {
await this.errCtrl.present(e)
}
} }
download() { download() {
@@ -83,6 +72,21 @@ export class SuccessPage {
this.api.exit() this.api.exit()
} }
private async complete() {
try {
const ret = await this.api.complete()
if (!this.isKiosk) {
this.torAddress = ret['tor-address']
this.lanAddress = ret['lan-address'].replace(/^https:/, 'http:')
this.cert = ret['root-ca']
await this.api.exit()
}
} catch (e: any) {
await this.errCtrl.present(e)
}
}
private initMatrix() { private initMatrix() {
this.ctx = this.canvas.nativeElement.getContext('2d')! this.ctx = this.canvas.nativeElement.getContext('2d')!
this.canvas.nativeElement.width = window.innerWidth this.canvas.nativeElement.width = window.innerWidth

View File

@@ -22,11 +22,17 @@
<ion-label class="label montserrat" routerLinkActive="label_selected"> <ion-label class="label montserrat" routerLinkActive="label_selected">
{{ page.title }} {{ page.title }}
</ion-label> </ion-label>
<ion-icon
*ngIf="page.url === '/system' && (warning$ | async)"
color="warning"
size="small"
name="warning"
></ion-icon>
<ion-icon <ion-icon
*ngIf="page.url === '/system' && (showEOSUpdate$ | async)" *ngIf="page.url === '/system' && (showEOSUpdate$ | async)"
color="success" color="success"
size="small" size="small"
name="rocket-outline" name="rocket"
></ion-icon> ></ion-icon>
<ion-badge <ion-badge
*ngIf="page.url === '/updates' && (updateCount$ | async) as updateCount" *ngIf="page.url === '/updates' && (updateCount$ | async) as updateCount"

View File

@@ -11,7 +11,9 @@ import {
filter, filter,
first, first,
map, map,
merge,
Observable, Observable,
of,
pairwise, pairwise,
startWith, startWith,
switchMap, switchMap,
@@ -22,6 +24,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
import { SplitPaneTracker } from 'src/app/services/split-pane.service' import { SplitPaneTracker } from 'src/app/services/split-pane.service'
import { Emver, THEME } from '@start9labs/shared' import { Emver, THEME } from '@start9labs/shared'
import { ConnectionService } from 'src/app/services/connection.service' import { ConnectionService } from 'src/app/services/connection.service'
import { ConfigService } from 'src/app/services/config.service'
@Component({ @Component({
selector: 'app-menu', selector: 'app-menu',
@@ -111,6 +114,11 @@ export class MenuComponent {
readonly theme$ = inject(THEME) readonly theme$ = inject(THEME)
readonly warning$ = merge(
of(this.config.isTorHttp()),
this.patch.watch$('server-info', 'ntp-synced').pipe(map(synced => !synced)),
)
constructor( constructor(
private readonly patch: PatchDB<DataModel>, private readonly patch: PatchDB<DataModel>,
private readonly eosService: EOSService, private readonly eosService: EOSService,
@@ -119,5 +127,6 @@ export class MenuComponent {
private readonly splitPane: SplitPaneTracker, private readonly splitPane: SplitPaneTracker,
private readonly emver: Emver, private readonly emver: Emver,
private readonly connectionService: ConnectionService, private readonly connectionService: ConnectionService,
private readonly config: ConfigService,
) {} ) {}
} }

View File

@@ -19,33 +19,35 @@ ion-card {
font-family: 'Open Sans'; font-family: 'Open Sans';
padding: 0.6rem; padding: 0.6rem;
font-weight: 600; font-weight: 600;
font-size: calc(12px + 0.5vw); height: 2.4rem;
height: 3rem;
} }
ion-card-content { ion-card-content {
min-height: 9rem; min-height: 8rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
ion-icon { ion-icon {
font-size: calc(90px + 0.5vw); font-size: calc(90px + 0.4vw);
--ionicon-stroke-width: 1rem; --ionicon-stroke-width: 1rem;
} }
} }
ion-footer { ion-footer {
padding: 1rem; padding: 0 1rem;
font-family: 'Open Sans'; font-family: 'Open Sans';
font-size: clamp(1rem, calc(12px + 0.5vw), 1.3rem); font-size: clamp(1rem, calc(12px + 0.5vw), 1.3rem);
height: 9rem; height: 4.5rem;
width: clamp(13rem, 80%, 18rem); width: clamp(13rem, 80%, 18rem);
margin: 0 auto; margin: 0 auto;
* { * {
max-width: 100%; max-width: 100%;
} }
p {
margin-top: 0;
}
} }
.footer-md::before { .footer-md::before {
@@ -54,9 +56,6 @@ ion-card {
} }
@media (max-width: 900px) { @media (max-width: 900px) {
ion-card-title, ion-footer {
height: auto !important;
}
ion-footer { ion-footer {
width: 10rem; width: 10rem;
} }

View File

@@ -1,14 +1,7 @@
<div #gridContent> <div #gridContent>
<ion-grid> <ion-grid>
<ion-row class="ion-justify-content-center ion-align-items-center"> <ion-row class="ion-justify-content-center ion-align-items-center">
<ion-col <ion-col *ngFor="let card of cards" sizeXs="12">
*ngFor="let card of cards"
responsiveCol
sizeLg="4"
sizeSm="6"
sizeXs="12"
class="ion-align-self-center"
>
<widget-card <widget-card
[cardDetails]="card" [cardDetails]="card"
[containerDimensions]="containerDimensions" [containerDimensions]="containerDimensions"

View File

@@ -3,11 +3,17 @@ ion-col {
--ion-grid-column-padding: 1rem; --ion-grid-column-padding: 1rem;
} }
@media (min-width: 1800px) { @media (min-width: 1700px) {
div { div {
padding: 0 20%; padding: 0 7%;
} }
ion-col { ion-col {
max-width: 24rem !important; max-width: 24rem !important;
} }
} }
@media (min-width: 2000px) {
div {
padding: 0 12%;
}
}

View File

@@ -38,33 +38,33 @@ export class WidgetListComponent {
cards: Card[] = [ cards: Card[] = [
{ {
title: 'Visit the Marketplace', title: 'Server Info',
icon: 'information-circle-outline',
color: 'var(--alt-green)',
description: 'View information about your server',
link: '/system/specs',
},
{
title: 'Browse',
icon: 'storefront-outline', icon: 'storefront-outline',
color: 'var(--alt-blue)', color: 'var(--alt-purple)',
description: 'Shop for your favorite open source services', description: 'Browse for services to install',
link: '/marketplace', link: '/marketplace',
qp: { back: 'true' }, qp: { back: 'true' },
}, },
{
title: 'Root CA',
icon: 'ribbon-outline',
color: 'var(--alt-orange)',
description: `Download and trust your server's root certificate authority`,
link: '/system/root-ca',
},
{ {
title: 'Create Backup', title: 'Create Backup',
icon: 'duplicate-outline', icon: 'duplicate-outline',
color: 'var(--alt-purple)', color: 'var(--alt-blue)',
description: 'Back up StartOS and service data', description: 'Back up StartOS and service data',
link: '/system/backup', link: '/system/backup',
}, },
{ {
title: 'Server Info', title: 'Monitor',
icon: 'information-circle-outline', icon: 'pulse-outline',
color: 'var(--alt-green)', color: 'var(--alt-orange)',
description: 'View basic information about your server', description: `View your system resource usage`,
link: '/system/specs', link: '/system/metrics',
}, },
{ {
title: 'User Manual', title: 'User Manual',
@@ -77,7 +77,7 @@ export class WidgetListComponent {
title: 'Contact Support', title: 'Contact Support',
icon: 'chatbubbles-outline', icon: 'chatbubbles-outline',
color: 'var(--alt-red)', color: 'var(--alt-red)',
description: 'Get help from the Start9 team and community', description: 'Get help from the Start9 community',
link: 'https://start9.com/contact', link: 'https://start9.com/contact',
}, },
] ]

View File

@@ -1,106 +1,78 @@
<ion-grid class="grid-wiz"> <ion-content>
<img width="60px" height="60px" src="/assets/img/icon.png" alt="StartOS" /> <ng-container *ngIf="!caTrusted; else trusted">
<ion-row> <ion-icon name="lock-closed-outline" class="wiz-icon"></ion-icon>
<ion-col class="ion-text-center"> <h1 class="title">Trust Your Root CA</h1>
<ion-icon name="lock-closed-outline" class="wiz-icon"></ion-icon> <p class="subtitle">
</ion-col> Download and trust your server's Root Certificate Authority to establish a
</ion-row> secure (
<ion-row> <b>HTTPS</b>
<ion-col class="ion-text-center"> ) connection. You will need to repeat this on every device you use to
<h2><b>Trust your Root Certificate Authority (CA)</b></h2> connect to your server.
<p> </p>
Download and trust your server's Root CA to establish secure, encrypted <ol>
( <li>
<b>HTTPS</b> <b>Download your server's Root CA</b>
) connections with your server . Your server uses its Root CA to generate SSL/TLS certificates for
</p> itself and installed services. These certificates are then used to
</ion-col> encrypt network traffic with your client devices.
</ion-row> <br />
<ion-row> <ion-button strong size="small" (click)="download()">
<ion-col sizeXs="12" sizeLg="4">
<div class="wiz-card">
<ion-row class="ion-justify-content-between">
<b class="wiz-step">1</b>
<tui-tooltip
content="Your server uses its Root CA to generate SSL/TLS certificates for itself and its installed services. These certificates are used to encrypt network traffic with your client devices."
direction="right"
></tui-tooltip>
</ion-row>
<div class="ion-text-center">
<h2>Download Root CA</h2>
<p>Download your server's Root CA</p>
</div>
<ion-button class="wiz-card-button" shape="round" (click)="download()">
<ion-icon slot="start" name="download-outline"></ion-icon>
Download Download
<ion-icon slot="end" name="download-outline"></ion-icon>
</ion-button> </ion-button>
</div> </li>
</ion-col> <li>
<ion-col sizeXs="12" sizeLg="4"> <b>Trust your server's Root CA</b>
<div class="wiz-card" [class.disabled]="!downloadClicked"> . Follow instructions for your OS. By trusting your server's Root CA,
<ion-row class="ion-justify-content-between"> your device can verify the authenticity of encrypted communications with
<b class="wiz-step">2</b> your server.
<tui-tooltip <br />
content="By trusting your server's Root CA, your device can verify the authenticity of its encrypted communications with your server and installed services. You will need to trust the Root CA on every device used to connect to your server." <ion-button strong size="small" (click)="instructions()">
direction="right" View Instructions
></tui-tooltip>
</ion-row>
<div class="ion-text-center">
<h2>Trust Root CA</h2>
<p>Follow instructions for your OS</p>
</div>
<ion-button
class="wiz-card-button"
shape="round"
(click)="instructions()"
[disabled]="!downloadClicked"
>
View Docs
<ion-icon slot="end" name="open-outline"></ion-icon> <ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button> </ion-button>
</div> </li>
</ion-col> <li>
<ion-col sizeXs="12" sizeLg="4"> <b>Test</b>
<div class="wiz-card" [class.disabled]="!polling && !caTrusted"> . If refreshing the page does not work, you may need to quit the browser
<b class="wiz-step">3</b> and re-open.
<div class="ion-text-center"> <i>Tip: before quitting, bookmark this page or copy the URL.</i>
<h2>Go To Login</h2> <br />
<p *ngIf="instructionsClicked; else space" class="inline-center"> <ion-button strong size="small" (click)="refresh()">
<ion-spinner Refresh
class="wiz-spinner" <ion-icon slot="end" name="refresh"></ion-icon>
*ngIf="!caTrusted; else trusted"
></ion-spinner>
<ng-template #trusted>
<ion-icon name="ribbon-outline" color="success"></ion-icon>
</ng-template>
&nbsp;{{ caTrusted ? 'Root CA trusted!' : 'Waiting for trust...' }}
</p>
<ng-template #space>
<!-- to keep alignment -->
<p><br /></p>
</ng-template>
</div>
<ion-button
class="wiz-card-button"
shape="round"
(click)="launchHttps()"
[disabled]="!caTrusted"
>
Open
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button> </ion-button>
</div> </li>
</ion-col> </ol>
</ion-row> <ion-button
<ion-row> style="--padding-start: 0"
<ion-col class="ion-text-center"> fill="clear"
<ion-button fill="clear" (click)="launchHttps()" [disabled]="caTrusted"> (click)="launchHttps()"
Skip [disabled]="caTrusted"
<ion-icon slot="end" name="open-outline"></ion-icon> >
</ion-button> Skip
</ion-col> <ion-icon slot="end" name="open-outline"></ion-icon>
</ion-row> </ion-button>
</ion-grid> </ng-container>
<ng-template #trusted>
<ion-icon
name="shield-checkmark-outline"
class="wiz-icon"
color="success"
></ion-icon>
<h1 class="title">Root CA Trusted!</h1>
<p class="subtitle">
You have successfully trusted your server's Root CA and may now log in
securely.
</p>
<ion-button strong (click)="launchHttps()">
Go to login
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</ng-template>
</ion-content>
<a <a
id="install-cert" id="install-cert"
href="/eos/local.crt" href="/eos/local.crt"

View File

@@ -1,44 +1,41 @@
.grid-wiz { ion-content {
--ion-grid-padding: 36px; --padding-top: 2.2rem;
height: 100% --padding-bottom: 2.2rem;
--padding-start: 2.2rem;
--padding-end: 2.2rem;
} }
.wiz-icon { .title {
font-size: 84px; font-size: 28px;
font-weight: 600;
} }
.wiz-card { .subtitle {
background: #414141; font-size: 21px;
margin: 24px; line-height: 26px;
padding: 16px; margin-bottom: 30px;
height: 280px; margin-top: 0;
border-radius: 16px; }
display: grid;
& h2 { ol {
font-weight: 600; font-size: 17px;
line-height: 25px;
li {
padding-bottom: 24px;
}
ion-button {
margin-top: 8px;
} }
} }
.wiz-card-button { a {
justify-self: center; cursor: pointer;
white-space: normal; color: aquamarine;
text-decoration: underline;
} }
.wiz-spinner { .wiz-icon {
width: 14px; font-size: 64px;
height: 14px;
}
.disabled {
filter: saturate(0.2) contrast(0.5)
}
.wiz-step {
margin-top: 4px;
}
.inline-center {
display: inline-flex;
align-items: center;
} }

View File

@@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { pauseFor, RELATIVE_URL } from '@start9labs/shared' import { RELATIVE_URL } from '@start9labs/shared'
import { DOCUMENT } from '@angular/common' import { DOCUMENT } from '@angular/common'
import { WINDOW } from '@ng-web-apis/common' import { WINDOW } from '@ng-web-apis/common'
@@ -11,9 +11,6 @@ import { WINDOW } from '@ng-web-apis/common'
styleUrls: ['./ca-wizard.component.scss'], styleUrls: ['./ca-wizard.component.scss'],
}) })
export class CAWizardComponent { export class CAWizardComponent {
downloadClicked = false
instructionsClicked = false
polling = false
caTrusted = false caTrusted = false
constructor( constructor(
@@ -25,15 +22,12 @@ export class CAWizardComponent {
) {} ) {}
async ngOnInit() { async ngOnInit() {
if (!this.config.isSecure()) { await this.testHttps().catch(e =>
await this.testHttps().catch(e => console.warn('Failed Https connection attempt'),
console.warn('Failed Https connection attempt'), )
)
}
} }
download() { download() {
this.downloadClicked = true
this.document.getElementById('install-cert')?.click() this.document.getElementById('install-cert')?.click()
} }
@@ -43,21 +37,10 @@ export class CAWizardComponent {
'_blank', '_blank',
'noreferrer', 'noreferrer',
) )
this.instructionsClicked = true
this.startDaemon()
} }
private async startDaemon(): Promise<void> { refresh() {
this.polling = true this.document.location.reload()
while (this.polling) {
try {
await this.testHttps()
this.polling = false
} catch (e) {
console.warn('Failed Https connection attempt')
await pauseFor(2000)
}
}
} }
launchHttps() { launchHttps() {
@@ -68,8 +51,6 @@ export class CAWizardComponent {
private async testHttps() { private async testHttps() {
const url = `https://${this.document.location.host}${this.relativeUrl}` const url = `https://${this.document.location.host}${this.relativeUrl}`
await this.api.echo({ message: 'ping' }, url).then(() => { await this.api.echo({ message: 'ping' }, url).then(() => {
this.downloadClicked = true
this.instructionsClicked = true
this.caTrusted = true this.caTrusted = true
}) })
} }