mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-04-01 21:13:09 +00:00
mok ux, autofill device and pf forms, docss for st, docs for start-sdk
This commit is contained in:
@@ -1,31 +1,72 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton, TuiDialogContext, TuiIcon } from '@taiga-ui/core'
|
||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
import { StateService } from '../services/state.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [TuiButton, TuiIcon, i18nPipe],
|
||||
template: `
|
||||
<div class="icon-container">
|
||||
<tui-icon icon="@tui.shield-check" class="mok-icon" />
|
||||
@if (!stateService.kiosk) {
|
||||
<div class="animation-container">
|
||||
<div class="port">
|
||||
<div class="port-inner"></div>
|
||||
</div>
|
||||
<div class="cable">
|
||||
<div class="cable-connector"></div>
|
||||
<div class="cable-body"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>
|
||||
{{
|
||||
'Connect a monitor and keyboard to your server before rebooting.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
} @else {
|
||||
<div class="icon-container">
|
||||
<tui-icon icon="@tui.monitor" class="monitor-icon" />
|
||||
</div>
|
||||
<p>
|
||||
{{ 'Keep your monitor connected for the next reboot.' | i18n }}
|
||||
</p>
|
||||
}
|
||||
|
||||
<div class="mok-info">
|
||||
<p>
|
||||
{{
|
||||
'Your system has Secure Boot enabled, which requires all kernel modules to be signed with a trusted key. Some hardware drivers — such as those for NVIDIA GPUs — are not signed by the default distribution key. Enrolling the StartOS signing key allows your firmware to trust these modules so your hardware can be fully utilized.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{
|
||||
'On the next boot, a blue screen (MokManager) will appear. You will have 10 seconds to select "Enroll MOK" before it dismisses.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
<p>
|
||||
{{
|
||||
'If you miss the window, simply reboot to try again. The blue screen will appear on every boot until the key is enrolled.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
<p class="steps-label">
|
||||
{{ 'After clicking "Enroll MOK":' | i18n }}
|
||||
</p>
|
||||
<ol>
|
||||
<li>Click "Continue"</li>
|
||||
<li>
|
||||
{{ 'When prompted, enter your StartOS password' | i18n }}
|
||||
</li>
|
||||
<li>Click "Reboot"</li>
|
||||
</ol>
|
||||
</div>
|
||||
<h3>{{ 'Secure Boot Key Enrollment' | i18n }}</h3>
|
||||
<p>
|
||||
{{
|
||||
'A signing key was enrolled for Secure Boot. On the next reboot, a blue screen (MokManager) will appear.'
|
||||
| i18n
|
||||
}}
|
||||
</p>
|
||||
<ol>
|
||||
<li>Select "Enroll MOK"</li>
|
||||
<li>Select "Continue"</li>
|
||||
<li>{{ 'Enter your StartOS master password when prompted' | i18n }}</li>
|
||||
<li>Select "Reboot"</li>
|
||||
</ol>
|
||||
|
||||
<footer>
|
||||
<button tuiButton (click)="context.completeWith(true)">
|
||||
{{ 'Got it' | i18n }}
|
||||
{{ 'Ok' | i18n }}
|
||||
</button>
|
||||
</footer>
|
||||
`,
|
||||
@@ -41,29 +82,114 @@ import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mok-icon {
|
||||
.monitor-icon {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
color: var(--tui-status-info);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0 0 0.5rem;
|
||||
.animation-container {
|
||||
position: relative;
|
||||
width: 160px;
|
||||
height: 69px;
|
||||
}
|
||||
|
||||
.port {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 28px;
|
||||
height: 18px;
|
||||
background: var(--tui-background-neutral-1);
|
||||
border: 2px solid var(--tui-border-normal);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.port-inner {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
background: var(--tui-background-neutral-2);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.cable {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
animation: slide-in 2s ease-in-out 0.5s infinite;
|
||||
left: 130px;
|
||||
}
|
||||
|
||||
.cable-connector {
|
||||
width: 18px;
|
||||
height: 12px;
|
||||
background: var(--tui-text-secondary);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.cable-body {
|
||||
width: 50px;
|
||||
height: 6px;
|
||||
background: var(--tui-text-tertiary);
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
|
||||
@keyframes slide-in {
|
||||
0% {
|
||||
left: 130px;
|
||||
opacity: 0;
|
||||
}
|
||||
5% {
|
||||
left: 130px;
|
||||
opacity: 1;
|
||||
}
|
||||
60% {
|
||||
left: 32px;
|
||||
opacity: 1;
|
||||
}
|
||||
80% {
|
||||
left: 32px;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
left: 32px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.mok-info {
|
||||
text-align: left;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
p {
|
||||
margin: 0 0 0.75rem;
|
||||
color: var(--tui-text-secondary);
|
||||
}
|
||||
|
||||
.steps-label {
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 500;
|
||||
color: var(--tui-text-primary);
|
||||
}
|
||||
|
||||
ol {
|
||||
margin: 0 0 1rem;
|
||||
padding-left: 1.5rem;
|
||||
|
||||
li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 1rem;
|
||||
color: var(--tui-text-secondary);
|
||||
}
|
||||
|
||||
ol {
|
||||
text-align: left;
|
||||
margin: 0 0 1.5rem;
|
||||
padding-left: 1.5rem;
|
||||
|
||||
li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
@@ -74,4 +200,5 @@ import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
})
|
||||
export class MokEnrollmentDialog {
|
||||
protected readonly context = injectContext<TuiDialogContext<boolean>>()
|
||||
readonly stateService = inject(StateService)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
DOCUMENT,
|
||||
ElementRef,
|
||||
inject,
|
||||
ViewChild,
|
||||
DOCUMENT,
|
||||
} from '@angular/core'
|
||||
import {
|
||||
DialogService,
|
||||
@@ -12,17 +12,17 @@ import {
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
} from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { TuiIcon, TuiLoader, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiAvatar } from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiCell, TuiHeader } from '@taiga-ui/layout'
|
||||
import { ApiService } from '../services/api.service'
|
||||
import { StateService } from '../services/state.service'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { DocumentationComponent } from '../components/documentation.component'
|
||||
import { MatrixComponent } from '../components/matrix.component'
|
||||
import { MokEnrollmentDialog } from '../components/mok-enrollment.dialog'
|
||||
import { RemoveMediaDialog } from '../components/remove-media.dialog'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { ApiService } from '../services/api.service'
|
||||
import { StateService } from '../services/state.service'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -50,7 +50,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
} @else {
|
||||
<!-- Step: Download Address Info (non-kiosk only) -->
|
||||
@if (!stateService.kiosk) {
|
||||
<button tuiCell="l" [disabled]="downloaded" (click)="download()">
|
||||
<button tuiCell="l" (click)="download()">
|
||||
<tui-avatar appearance="secondary" src="@tui.download" />
|
||||
<div tuiTitle>
|
||||
{{ 'Download Address Info' | i18n }}
|
||||
@@ -67,12 +67,12 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Step: Remove USB Media (when restart needed) -->
|
||||
<!-- Step: Restart flow -->
|
||||
@if (result.needsRestart) {
|
||||
<button
|
||||
tuiCell="l"
|
||||
[class.disabled]="!stateService.kiosk && !downloaded"
|
||||
[disabled]="(!stateService.kiosk && !downloaded) || usbRemoved"
|
||||
[disabled]="!stateService.kiosk && !downloaded"
|
||||
(click)="removeMedia()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.usb" />
|
||||
@@ -90,11 +90,39 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
}
|
||||
</button>
|
||||
|
||||
<!-- Step: Secure Boot Enrollment (when MOK enrolled) -->
|
||||
@if (stateService.mokEnrolled) {
|
||||
<button
|
||||
tuiCell="l"
|
||||
[class.disabled]="!usbRemoved"
|
||||
[disabled]="!usbRemoved"
|
||||
(click)="acknowledgeMok()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.shield-check" />
|
||||
<div tuiTitle>
|
||||
{{ 'Secure Boot Enrollment' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
{{
|
||||
'Prepare for Secure Boot key enrollment on the next reboot'
|
||||
| i18n
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
@if (mokAcknowledged) {
|
||||
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
||||
}
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Step: Restart Server -->
|
||||
<button
|
||||
tuiCell="l"
|
||||
[class.disabled]="!usbRemoved"
|
||||
[disabled]="!usbRemoved || rebooted || rebooting"
|
||||
[class.disabled]="
|
||||
!usbRemoved || (stateService.mokEnrolled && !mokAcknowledged)
|
||||
"
|
||||
[disabled]="
|
||||
!usbRemoved || (stateService.mokEnrolled && !mokAcknowledged)
|
||||
"
|
||||
(click)="reboot()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.rotate-cw" />
|
||||
@@ -116,6 +144,16 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
<tui-icon icon="@tui.circle-check" class="g-positive" />
|
||||
}
|
||||
</button>
|
||||
} @else if (stateService.kiosk) {
|
||||
<button tuiCell="l" (click)="exitKiosk()">
|
||||
<tui-avatar appearance="secondary" src="@tui.log-in" />
|
||||
<div tuiTitle>
|
||||
{{ 'Continue to Login' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
{{ 'Proceed to the StartOS login screen' | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Step: Open Local Address (non-kiosk only) -->
|
||||
@@ -137,22 +175,6 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
}
|
||||
|
||||
<!-- Step: Continue to Login (kiosk only) -->
|
||||
@if (stateService.kiosk) {
|
||||
<button
|
||||
tuiCell="l"
|
||||
[class.disabled]="result.needsRestart && !rebooted"
|
||||
[disabled]="result.needsRestart && !rebooted"
|
||||
(click)="exitKiosk()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.log-in" />
|
||||
<div tuiTitle>
|
||||
{{ 'Continue to Login' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
{{ 'Proceed to the StartOS login screen' | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
`,
|
||||
@@ -198,6 +220,7 @@ export default class SuccessPage implements AfterViewInit {
|
||||
lanAddress = ''
|
||||
downloaded = false
|
||||
usbRemoved = false
|
||||
mokAcknowledged = false
|
||||
rebooting = false
|
||||
rebooted = false
|
||||
|
||||
@@ -212,8 +235,6 @@ export default class SuccessPage implements AfterViewInit {
|
||||
}
|
||||
|
||||
download() {
|
||||
if (this.downloaded) return
|
||||
|
||||
const lanElem = this.document.getElementById('lan-addr')
|
||||
if (lanElem) lanElem.innerHTML = this.lanAddress
|
||||
|
||||
@@ -243,6 +264,19 @@ export default class SuccessPage implements AfterViewInit {
|
||||
})
|
||||
}
|
||||
|
||||
acknowledgeMok() {
|
||||
this.dialogs
|
||||
.openComponent<boolean>(new PolymorpheusComponent(MokEnrollmentDialog), {
|
||||
label: 'Secure Boot',
|
||||
size: 'm',
|
||||
dismissible: false,
|
||||
closeable: false,
|
||||
})
|
||||
.subscribe(() => {
|
||||
this.mokAcknowledged = true
|
||||
})
|
||||
}
|
||||
|
||||
exitKiosk() {
|
||||
this.api.exit()
|
||||
}
|
||||
@@ -252,6 +286,8 @@ export default class SuccessPage implements AfterViewInit {
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
if (this.rebooting || this.rebooted) return
|
||||
|
||||
this.rebooting = true
|
||||
|
||||
try {
|
||||
@@ -276,20 +312,6 @@ export default class SuccessPage implements AfterViewInit {
|
||||
await this.api.exit()
|
||||
}
|
||||
}
|
||||
|
||||
if (this.stateService.mokEnrolled && this.result.needsRestart) {
|
||||
this.dialogs
|
||||
.openComponent<boolean>(
|
||||
new PolymorpheusComponent(MokEnrollmentDialog),
|
||||
{
|
||||
label: 'Secure Boot',
|
||||
size: 's',
|
||||
dismissible: false,
|
||||
closeable: true,
|
||||
},
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ export class MockApiService extends ApiService {
|
||||
return {
|
||||
guid: 'mock-data-guid',
|
||||
attach: !params.dataDrive.wipe,
|
||||
mokEnrolled: false,
|
||||
mokEnrolled: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user