mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 12:33:40 +00:00
chore: update Taiga to 5 (#3136)
* chore: update Taiga to 5 * chore: fix
This commit is contained in:
@@ -8,7 +8,7 @@ import { StateService } from './services/state.service'
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: '<tui-root tuiTheme="dark"><router-outlet /></tui-root>',
|
||||
template: '<tui-root><router-outlet /></tui-root>',
|
||||
imports: [TuiRoot, RouterOutlet],
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
provideZoneChangeDetection,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import { provideAnimations } from '@angular/platform-browser/animations'
|
||||
import {
|
||||
PreloadAllModules,
|
||||
provideRouter,
|
||||
@@ -29,8 +28,10 @@ import {
|
||||
import {
|
||||
tuiButtonOptionsProvider,
|
||||
tuiTextfieldOptionsProvider,
|
||||
provideTaiga,
|
||||
tuiHintOptionsProvider,
|
||||
tuiDialogOptionsProvider,
|
||||
} from '@taiga-ui/core'
|
||||
import { provideEventPlugins } from '@taiga-ui/event-plugins'
|
||||
|
||||
import { ROUTES } from './app.routes'
|
||||
import { ApiService } from './services/api.service'
|
||||
@@ -47,8 +48,9 @@ const version = require('../../../../package.json').version
|
||||
export const APP_CONFIG: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection(),
|
||||
provideAnimations(),
|
||||
provideEventPlugins(),
|
||||
provideTaiga({ mode: 'dark' }),
|
||||
tuiHintOptionsProvider({ appearance: 'primary-grayscale' }),
|
||||
tuiDialogOptionsProvider({ size: 's' }),
|
||||
provideRouter(
|
||||
ROUTES,
|
||||
withDisabledInitialNavigation(),
|
||||
|
||||
@@ -13,14 +13,10 @@ import {
|
||||
TuiDialogContext,
|
||||
TuiError,
|
||||
TuiIcon,
|
||||
TuiTextfield,
|
||||
TuiInput,
|
||||
tuiValidationErrorsProvider,
|
||||
} from '@taiga-ui/core'
|
||||
import {
|
||||
TUI_VALIDATION_ERRORS,
|
||||
TuiButtonLoading,
|
||||
TuiFieldErrorPipe,
|
||||
TuiPassword,
|
||||
} from '@taiga-ui/kit'
|
||||
import { TuiButtonLoading, TuiPassword } from '@taiga-ui/kit'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { ApiService } from '../services/api.service'
|
||||
import { StartOSDiskInfoWithId } from '../types'
|
||||
@@ -36,42 +32,36 @@ export interface CifsResult {
|
||||
<tui-textfield>
|
||||
<label tuiLabel>{{ 'Hostname' | i18n }}*</label>
|
||||
<input
|
||||
tuiTextfield
|
||||
tuiInput
|
||||
formControlName="hostname"
|
||||
placeholder="e.g. 'My Computer' OR 'my-computer.local'"
|
||||
/>
|
||||
</tui-textfield>
|
||||
<tui-error
|
||||
formControlName="hostname"
|
||||
[error]="['required'] | tuiFieldError | async"
|
||||
/>
|
||||
<tui-error formControlName="hostname" [order]="['required']" />
|
||||
|
||||
<tui-textfield class="input">
|
||||
<label tuiLabel>{{ 'Path' | i18n }}*</label>
|
||||
<input
|
||||
tuiTextfield
|
||||
tuiInput
|
||||
formControlName="path"
|
||||
placeholder="/Desktop/my-folder"
|
||||
/>
|
||||
</tui-textfield>
|
||||
<tui-error formControlName="path" [error]="[] | tuiFieldError | async" />
|
||||
<tui-error formControlName="path" />
|
||||
|
||||
<tui-textfield class="input">
|
||||
<label tuiLabel>{{ 'Username' | i18n }}*</label>
|
||||
<input
|
||||
tuiTextfield
|
||||
tuiInput
|
||||
formControlName="username"
|
||||
placeholder="Enter username"
|
||||
/>
|
||||
</tui-textfield>
|
||||
<tui-error
|
||||
formControlName="username"
|
||||
[error]="[] | tuiFieldError | async"
|
||||
/>
|
||||
<tui-error formControlName="username" />
|
||||
|
||||
<tui-textfield class="input">
|
||||
<label tuiLabel>{{ 'Password' | i18n }}</label>
|
||||
<input tuiTextfield type="password" formControlName="password" />
|
||||
<input tuiInput type="password" formControlName="password" />
|
||||
<tui-icon tuiPassword />
|
||||
</tui-textfield>
|
||||
|
||||
@@ -107,20 +97,16 @@ export interface CifsResult {
|
||||
ReactiveFormsModule,
|
||||
TuiButton,
|
||||
TuiButtonLoading,
|
||||
TuiTextfield,
|
||||
TuiInput,
|
||||
TuiPassword,
|
||||
TuiError,
|
||||
TuiFieldErrorPipe,
|
||||
TuiIcon,
|
||||
i18nPipe,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: TUI_VALIDATION_ERRORS,
|
||||
useValue: {
|
||||
required: 'This field is required',
|
||||
},
|
||||
},
|
||||
tuiValidationErrorsProvider({
|
||||
required: 'This field is required',
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class CifsComponent {
|
||||
@@ -183,10 +169,7 @@ export class CifsComponent {
|
||||
this.dialogs
|
||||
.openAlert(
|
||||
'Unable to connect to network folder. Ensure (1) target computer is connected to LAN, (2) target folder is being shared, and (3) hostname, path, and credentials are accurate.',
|
||||
{
|
||||
label: 'Connection Failed',
|
||||
size: 's',
|
||||
},
|
||||
{ label: 'Connection Failed' },
|
||||
)
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { Component } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiDialogContext } from '@taiga-ui/core'
|
||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
|
||||
@Component({
|
||||
imports: [TuiButton, i18nPipe],
|
||||
imports: [TuiButton, TuiHeader, TuiTitle, i18nPipe],
|
||||
template: `
|
||||
<p>{{ 'This drive contains existing StartOS data.' | i18n }}</p>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h2 [id]="context.id">{{ 'StartOS Data Detected' | i18n }}</h2>
|
||||
<p>{{ 'This drive contains existing StartOS data.' | i18n }}</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<ul>
|
||||
<li>
|
||||
<strong class="g-positive">{{ 'Preserve' | i18n }}</strong>
|
||||
@@ -28,30 +34,19 @@ import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
</button>
|
||||
<button
|
||||
tuiButton
|
||||
class="preserve-btn"
|
||||
appearance=""
|
||||
[style.background]="'var(--tui-status-positive)'"
|
||||
(click)="context.completeWith(true)"
|
||||
>
|
||||
{{ 'Preserve' | i18n }}
|
||||
</button>
|
||||
</footer>
|
||||
`,
|
||||
styles: `
|
||||
p {
|
||||
margin: 0 0 0.75rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
margin-top: 2rem;
|
||||
gap: 0.5rem;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.preserve-btn {
|
||||
background: var(--tui-status-positive) !important;
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class PreserveOverwriteDialog {
|
||||
protected readonly context = injectContext<TuiDialogContext<boolean>>()
|
||||
}
|
||||
|
||||
export const PRESERVE_OVERWRITE = new PolymorpheusComponent(
|
||||
PreserveOverwriteDialog,
|
||||
)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { Component } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiDialogContext, TuiTextfield } from '@taiga-ui/core'
|
||||
import { TuiDialogContext, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiDataListWrapper, TuiSelect } from '@taiga-ui/kit'
|
||||
import { TuiHeader } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { StartOSDiskInfoWithId } from '../types'
|
||||
|
||||
@@ -11,36 +12,48 @@ interface Data {
|
||||
}
|
||||
|
||||
@Component({
|
||||
imports: [FormsModule, TuiTextfield, TuiSelect, TuiDataListWrapper, i18nPipe],
|
||||
imports: [
|
||||
FormsModule,
|
||||
TuiSelect,
|
||||
TuiDataListWrapper,
|
||||
i18nPipe,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
],
|
||||
template: `
|
||||
<p>{{ 'Multiple backups found. Select which one to restore.' | i18n }}</p>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h2 [id]="context.id">{{ 'Select Network Backup' | i18n }}</h2>
|
||||
<p>
|
||||
{{ 'Multiple backups found. Select which one to restore.' | i18n }}
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<tui-textfield [stringify]="stringify">
|
||||
<label tuiLabel>{{ 'Backups' | i18n }}</label>
|
||||
<input tuiSelect [(ngModel)]="selectedServer" />
|
||||
<tui-data-list-wrapper
|
||||
new
|
||||
*tuiTextfieldDropdown
|
||||
*tuiDropdown
|
||||
[items]="context.data.servers"
|
||||
[itemContent]="serverContent"
|
||||
/>
|
||||
</tui-textfield>
|
||||
|
||||
<ng-template #serverContent let-server>
|
||||
<div class="server-item">
|
||||
<span>{{ server.id }}</span>
|
||||
<span tuiTitle>
|
||||
{{ server.id }}
|
||||
<!-- @TODO eos-version? -->
|
||||
<small>{{ server['eos-version'] }}</small>
|
||||
</div>
|
||||
@if (server['eos-version']) {
|
||||
<span tuiSubtitle>
|
||||
{{ server['eos-version'] }}
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
</ng-template>
|
||||
`,
|
||||
styles: `
|
||||
.server-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
div {
|
||||
margin-block-end: 1rem;
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
@@ -1,32 +1,27 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDialogContext,
|
||||
TuiIcon,
|
||||
TuiTextfield,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiButton, TuiDialogContext, TuiIcon, TuiInput } from '@taiga-ui/core'
|
||||
import { TuiPassword } from '@taiga-ui/kit'
|
||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
|
||||
@Component({
|
||||
imports: [
|
||||
FormsModule,
|
||||
TuiButton,
|
||||
TuiTextfield,
|
||||
TuiPassword,
|
||||
TuiIcon,
|
||||
i18nPipe,
|
||||
],
|
||||
imports: [FormsModule, TuiButton, TuiInput, TuiPassword, TuiIcon, i18nPipe],
|
||||
template: `
|
||||
<p>
|
||||
{{ 'Enter the password that was used to encrypt this backup.' | i18n }}
|
||||
</p>
|
||||
<header tuiHeader>
|
||||
<hgroup tuiTitle>
|
||||
<h2 [id]="context.id">{{ 'Unlock Backup' | i18n }}</h2>
|
||||
<p>
|
||||
{{
|
||||
'Enter the password that was used to encrypt this backup.' | i18n
|
||||
}}
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<tui-textfield>
|
||||
<label tuiLabel>{{ 'Password' | i18n }}</label>
|
||||
<input
|
||||
tuiTextfield
|
||||
tuiInput
|
||||
type="password"
|
||||
[(ngModel)]="password"
|
||||
(keyup.enter)="unlock()"
|
||||
@@ -62,3 +57,5 @@ export class UnlockPasswordDialog {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const UNLOCK_PASSWORD = new PolymorpheusComponent(UnlockPasswordDialog)
|
||||
|
||||
@@ -12,24 +12,29 @@ import {
|
||||
ErrorService,
|
||||
i18nKey,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
toGuid,
|
||||
} from '@start9labs/shared'
|
||||
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TuiLoader,
|
||||
TuiTextfield,
|
||||
TuiInput,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiDataListWrapper, TuiSelect, TuiTooltip } from '@taiga-ui/kit'
|
||||
import {
|
||||
TuiDataListWrapper,
|
||||
TuiNotificationMiddleService,
|
||||
TuiSelect,
|
||||
TuiTooltip,
|
||||
} from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { filter, Subscription } from 'rxjs'
|
||||
import { ApiService } from '../services/api.service'
|
||||
import { StateService } from '../services/state.service'
|
||||
import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog'
|
||||
import { PRESERVE_OVERWRITE } from '../components/preserve-overwrite.dialog'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -42,7 +47,7 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
|
||||
@if (loading) {
|
||||
<tui-loader />
|
||||
} @else if (drives.length === 0) {
|
||||
<p class="no-drives">
|
||||
<p tuiNotification size="m" appearance="warning">
|
||||
{{
|
||||
'No drives found. Please connect a drive and click Refresh.'
|
||||
| i18n
|
||||
@@ -70,8 +75,7 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
|
||||
}
|
||||
@if (!mobile) {
|
||||
<tui-data-list-wrapper
|
||||
new
|
||||
*tuiTextfieldDropdown
|
||||
*tuiDropdown
|
||||
[items]="drives"
|
||||
[itemContent]="driveContent"
|
||||
/>
|
||||
@@ -100,36 +104,27 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
|
||||
}
|
||||
@if (!mobile) {
|
||||
<tui-data-list-wrapper
|
||||
new
|
||||
*tuiTextfieldDropdown
|
||||
*tuiDropdown
|
||||
[items]="drives"
|
||||
[itemContent]="driveContent"
|
||||
/>
|
||||
}
|
||||
@if (preserveData === true) {
|
||||
<tui-icon
|
||||
icon="@tui.database"
|
||||
style="color: var(--tui-status-positive); pointer-events: none"
|
||||
/>
|
||||
<tui-icon icon="@tui.database" class="g-positive" />
|
||||
}
|
||||
@if (preserveData === false) {
|
||||
<tui-icon
|
||||
icon="@tui.database-zap"
|
||||
style="color: var(--tui-status-negative); pointer-events: none"
|
||||
/>
|
||||
<tui-icon icon="@tui.database-zap" class="g-negative" />
|
||||
}
|
||||
<tui-icon [tuiTooltip]="dataDriveTooltip" />
|
||||
</tui-textfield>
|
||||
|
||||
<ng-template #driveContent let-drive>
|
||||
<div class="drive-item">
|
||||
<span class="drive-name">
|
||||
{{ driveName(drive) }}
|
||||
</span>
|
||||
<small>
|
||||
<span tuiTitle>
|
||||
{{ driveName(drive) }}
|
||||
<span tuiSubtitle>
|
||||
{{ formatCapacity(drive.capacity) }} · {{ drive.logicalname }}
|
||||
</small>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
}
|
||||
|
||||
@@ -152,19 +147,8 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
|
||||
}
|
||||
`,
|
||||
styles: `
|
||||
.no-drives {
|
||||
text-align: center;
|
||||
color: var(--tui-text-secondary);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.drive-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
tui-icon:not([tuiTooltip]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
`,
|
||||
imports: [
|
||||
@@ -173,7 +157,8 @@ import { PreserveOverwriteDialog } from '../components/preserve-overwrite.dialog
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TuiLoader,
|
||||
TuiTextfield,
|
||||
TuiInput,
|
||||
TuiNotification,
|
||||
TuiSelect,
|
||||
TuiDataListWrapper,
|
||||
TuiTooltip,
|
||||
@@ -186,13 +171,13 @@ export default class DrivesPage {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly router = inject(Router)
|
||||
private readonly dialogs = inject(DialogService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly loader = inject(TuiNotificationMiddleService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly stateService = inject(StateService)
|
||||
private readonly cdr = inject(ChangeDetectorRef)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
protected readonly mobile = inject(TUI_IS_MOBILE)
|
||||
protected readonly mobile = inject(WA_IS_MOBILE)
|
||||
|
||||
@HostListener('document:keydown', ['$event'])
|
||||
onKeydown(event: KeyboardEvent) {
|
||||
@@ -308,38 +293,27 @@ export default class DrivesPage {
|
||||
private showPreserveOverwriteDialog() {
|
||||
let selectionMade = false
|
||||
|
||||
this.dialogs
|
||||
.openComponent<boolean>(
|
||||
new PolymorpheusComponent(PreserveOverwriteDialog),
|
||||
{
|
||||
label: 'StartOS Data Detected',
|
||||
size: 's',
|
||||
dismissible: true,
|
||||
closeable: true,
|
||||
},
|
||||
)
|
||||
.subscribe({
|
||||
next: preserve => {
|
||||
selectionMade = true
|
||||
this.preserveData = preserve
|
||||
this.dialogs.openComponent<boolean>(PRESERVE_OVERWRITE).subscribe({
|
||||
next: preserve => {
|
||||
selectionMade = true
|
||||
this.preserveData = preserve
|
||||
this.cdr.markForCheck()
|
||||
},
|
||||
complete: () => {
|
||||
if (!selectionMade) {
|
||||
// Dialog was dismissed without selection - clear the data drive
|
||||
this.selectedDataDrive = null
|
||||
this.preserveData = null
|
||||
this.cdr.markForCheck()
|
||||
},
|
||||
complete: () => {
|
||||
if (!selectionMade) {
|
||||
// Dialog was dismissed without selection - clear the data drive
|
||||
this.selectedDataDrive = null
|
||||
this.preserveData = null
|
||||
this.cdr.markForCheck()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
private showOsDriveWarning() {
|
||||
this.dialogs
|
||||
.openConfirm({
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
content: `<ul>
|
||||
<li class="g-negative">${this.i18n.transform('Data on the OS drive may be overwritten.')}</li>
|
||||
@@ -363,7 +337,6 @@ export default class DrivesPage {
|
||||
this.dialogs
|
||||
.openConfirm({
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
content: message as i18nKey,
|
||||
yes: 'Continue',
|
||||
@@ -397,10 +370,9 @@ export default class DrivesPage {
|
||||
this.dialogSub = this.dialogs
|
||||
.openAlert('StartOS has been installed successfully.', {
|
||||
label: 'Installation Complete!',
|
||||
size: 's',
|
||||
dismissible: false,
|
||||
closeable: true,
|
||||
data: { button: this.i18n.transform('Continue to Setup') },
|
||||
closable: true,
|
||||
data: this.i18n.transform('Continue to Setup'),
|
||||
})
|
||||
.subscribe({
|
||||
complete: () => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiAppearance, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiTitle, TuiCell } from '@taiga-ui/core'
|
||||
import { TuiAvatar } from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiCell, TuiHeader } from '@taiga-ui/layout'
|
||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||
import { StateService } from '../services/state.service'
|
||||
|
||||
@Component({
|
||||
@@ -14,7 +14,7 @@ import { StateService } from '../services/state.service'
|
||||
</header>
|
||||
|
||||
<button tuiCell="l" (click)="startFresh()">
|
||||
<tui-avatar appearance="positive" src="@tui.plus" />
|
||||
<span tuiAvatar="@tui.plus" appearance="positive"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Start Fresh' | i18n }}
|
||||
<div tuiSubtitle>{{ 'Set up a brand new server' | i18n }}</div>
|
||||
@@ -22,7 +22,7 @@ import { StateService } from '../services/state.service'
|
||||
</button>
|
||||
|
||||
<button tuiCell="l" (click)="restore()">
|
||||
<tui-avatar appearance="warning" src="@tui.archive-restore" />
|
||||
<span tuiAvatar="@tui.archive-restore" appearance="warning"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Restore from Backup' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
@@ -32,7 +32,7 @@ import { StateService } from '../services/state.service'
|
||||
</button>
|
||||
|
||||
<button tuiCell="l" (click)="transfer()">
|
||||
<tui-avatar appearance="info" src="@tui.hard-drive-download" />
|
||||
<span tuiAvatar="@tui.hard-drive-download" appearance="info"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Transfer' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
@@ -42,15 +42,7 @@ import { StateService } from '../services/state.service'
|
||||
</button>
|
||||
</div>
|
||||
`,
|
||||
imports: [
|
||||
TuiAppearance,
|
||||
TuiCardLarge,
|
||||
TuiHeader,
|
||||
TuiCell,
|
||||
TuiTitle,
|
||||
TuiAvatar,
|
||||
i18nPipe,
|
||||
],
|
||||
imports: [TuiCardLarge, TuiHeader, TuiCell, TuiTitle, TuiAvatar, i18nPipe],
|
||||
})
|
||||
export default class HomePage {
|
||||
private readonly router = inject(Router)
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
Keyboard,
|
||||
LanguageCode,
|
||||
} from '@start9labs/shared'
|
||||
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core'
|
||||
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
|
||||
import { TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||
import {
|
||||
TuiButtonLoading,
|
||||
TuiChevron,
|
||||
@@ -36,11 +36,7 @@ import { StateService } from '../services/state.service'
|
||||
<input tuiSelect [(ngModel)]="selected" />
|
||||
}
|
||||
@if (!mobile) {
|
||||
<tui-data-list-wrapper
|
||||
new
|
||||
*tuiTextfieldDropdown
|
||||
[items]="keyboards"
|
||||
/>
|
||||
<tui-data-list-wrapper *tuiDropdown [items]="keyboards" />
|
||||
}
|
||||
</tui-textfield>
|
||||
|
||||
@@ -61,7 +57,6 @@ import { StateService } from '../services/state.service'
|
||||
TuiCardLarge,
|
||||
TuiButton,
|
||||
TuiButtonLoading,
|
||||
TuiTextfield,
|
||||
TuiChevron,
|
||||
TuiSelect,
|
||||
TuiDataListWrapper,
|
||||
@@ -74,7 +69,7 @@ export default class KeyboardPage {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly stateService = inject(StateService)
|
||||
|
||||
protected readonly mobile = inject(TUI_IS_MOBILE)
|
||||
protected readonly mobile = inject(WA_IS_MOBILE)
|
||||
// All keyboards, with language-specific keyboards at the top
|
||||
readonly keyboards = getAllKeyboardsSorted(
|
||||
this.stateService.language as LanguageCode,
|
||||
|
||||
@@ -2,9 +2,10 @@ import { Component, computed, inject, signal } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { i18nPipe, i18nService, Language, LANGUAGES } from '@start9labs/shared'
|
||||
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||
import { TuiButton, TuiTextfield, TuiTitle } from '@taiga-ui/core'
|
||||
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
|
||||
import { TuiButton, TuiCell, TuiTitle } from '@taiga-ui/core'
|
||||
import {
|
||||
TuiAvatar,
|
||||
TuiButtonLoading,
|
||||
TuiChevron,
|
||||
TuiDataListWrapper,
|
||||
@@ -18,13 +19,15 @@ import { StateService } from '../services/state.service'
|
||||
template: `
|
||||
<section tuiCardLarge="compact">
|
||||
<header tuiHeader>
|
||||
<h2 tuiTitle>
|
||||
<span class="inline-title">
|
||||
<img src="assets/img/icon.png" alt="Start9" />
|
||||
<hgroup tuiTitle>
|
||||
<h2 tuiCell="m">
|
||||
<span tuiAvatar>
|
||||
<img src="assets/img/icon.png" alt="Start9" />
|
||||
</span>
|
||||
{{ 'Welcome to' | i18n }} StartOS
|
||||
</span>
|
||||
<span tuiSubtitle>{{ 'Select your language' | i18n }}</span>
|
||||
</h2>
|
||||
</h2>
|
||||
<p tuiSubtitle>{{ 'Select your language' | i18n }}</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
<tui-textfield
|
||||
tuiChevron
|
||||
@@ -48,8 +51,7 @@ import { StateService } from '../services/state.service'
|
||||
}
|
||||
@if (!mobile) {
|
||||
<tui-data-list-wrapper
|
||||
*tuiTextfieldDropdown
|
||||
new
|
||||
*tuiDropdown
|
||||
[items]="languages"
|
||||
[itemContent]="itemContent"
|
||||
/>
|
||||
@@ -57,11 +59,10 @@ import { StateService } from '../services/state.service'
|
||||
</tui-textfield>
|
||||
|
||||
<ng-template #itemContent let-item>
|
||||
@let lang = asLanguage(item);
|
||||
<div class="language-item">
|
||||
<span>{{ lang.nativeName }}</span>
|
||||
<small>{{ lang.name | i18n }}</small>
|
||||
</div>
|
||||
<span tuiTitle>
|
||||
{{ asLanguage(item).nativeName }}
|
||||
<span tuiSubtitle>{{ asLanguage(item).name | i18n }}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
|
||||
<footer>
|
||||
@@ -76,22 +77,13 @@ import { StateService } from '../services/state.service'
|
||||
</footer>
|
||||
</section>
|
||||
`,
|
||||
styles: `
|
||||
.language-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
`,
|
||||
imports: [
|
||||
FormsModule,
|
||||
TuiCardLarge,
|
||||
TuiButton,
|
||||
TuiButtonLoading,
|
||||
TuiTextfield,
|
||||
TuiAvatar,
|
||||
TuiCell,
|
||||
TuiChevron,
|
||||
TuiSelect,
|
||||
TuiDataListWrapper,
|
||||
@@ -106,7 +98,7 @@ export default class LanguagePage {
|
||||
private readonly stateService = inject(StateService)
|
||||
private readonly i18nService = inject(i18nService)
|
||||
|
||||
protected readonly mobile = inject(TUI_IS_MOBILE)
|
||||
protected readonly mobile = inject(WA_IS_MOBILE)
|
||||
readonly languages = LANGUAGES
|
||||
|
||||
selected =
|
||||
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
getErrorMessage,
|
||||
i18nPipe,
|
||||
InitializingComponent,
|
||||
LoadingService,
|
||||
} from '@start9labs/shared'
|
||||
import { T } from '@start9labs/start-sdk'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
|
||||
import {
|
||||
catchError,
|
||||
filter,
|
||||
@@ -64,7 +64,7 @@ import { StateService } from '../services/state.service'
|
||||
})
|
||||
export default class LoadingPage {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly loader = inject(TuiNotificationMiddleService)
|
||||
private readonly dialog = inject(DialogService)
|
||||
private readonly router = inject(Router)
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AsyncPipe } from '@angular/common'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import {
|
||||
@@ -8,31 +7,28 @@ import {
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms'
|
||||
import {
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
LoadingService,
|
||||
normalizeHostname,
|
||||
} from '@start9labs/shared'
|
||||
import { ErrorService, i18nPipe, normalizeHostname } from '@start9labs/shared'
|
||||
import { TuiAutoFocus, TuiMapperPipe, TuiValidator } from '@taiga-ui/cdk'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiError,
|
||||
TuiIcon,
|
||||
TuiTextfield,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import {
|
||||
TuiFieldErrorPipe,
|
||||
TuiPassword,
|
||||
TuiInput,
|
||||
tuiValidationErrorsProvider,
|
||||
} from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiNotificationMiddleService, TuiPassword } from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiForm, TuiHeader } from '@taiga-ui/layout'
|
||||
import { StateService } from '../services/state.service'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<section tuiCardLarge="compact">
|
||||
<form
|
||||
tuiCardLarge="compact"
|
||||
tuiForm
|
||||
[formGroup]="form"
|
||||
(ngSubmit)="submit()"
|
||||
>
|
||||
<header tuiHeader>
|
||||
<h2 tuiTitle>
|
||||
{{
|
||||
@@ -43,104 +39,80 @@ import { StateService } from '../services/state.service'
|
||||
</h2>
|
||||
</header>
|
||||
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
@if (isFresh) {
|
||||
<tui-textfield>
|
||||
<label tuiLabel>{{ 'Server Name' | i18n }}</label>
|
||||
<input tuiTextfield tuiAutoFocus formControlName="name" />
|
||||
</tui-textfield>
|
||||
<tui-error
|
||||
formControlName="name"
|
||||
[error]="[] | tuiFieldError | async"
|
||||
/>
|
||||
@if (form.controls.name.value?.trim()) {
|
||||
<p class="hostname-preview">{{ derivedHostname }}.local</p>
|
||||
}
|
||||
@if (isFresh) {
|
||||
<tui-textfield>
|
||||
<label tuiLabel>{{ 'Server Name' | i18n }}</label>
|
||||
<input tuiInput tuiAutoFocus formControlName="name" />
|
||||
</tui-textfield>
|
||||
<tui-error formControlName="name" />
|
||||
@if (form.controls.name.value?.trim()) {
|
||||
<tui-error class="g-secondary" error="{{ derivedHostname }}.local" />
|
||||
}
|
||||
}
|
||||
|
||||
<tui-textfield [style.margin-top.rem]="isFresh ? 1 : 0">
|
||||
<label tuiLabel>
|
||||
{{ isFresh ? ('Password' | i18n) : ('New Password' | i18n) }}
|
||||
</label>
|
||||
<input
|
||||
tuiTextfield
|
||||
type="password"
|
||||
[tuiAutoFocus]="!isFresh"
|
||||
maxlength="64"
|
||||
formControlName="password"
|
||||
/>
|
||||
<tui-icon tuiPassword />
|
||||
</tui-textfield>
|
||||
<tui-error
|
||||
<tui-textfield>
|
||||
<label tuiLabel>
|
||||
{{ isFresh ? ('Password' | i18n) : ('New Password' | i18n) }}
|
||||
</label>
|
||||
<input
|
||||
tuiInput
|
||||
type="password"
|
||||
[tuiAutoFocus]="!isFresh"
|
||||
maxlength="64"
|
||||
formControlName="password"
|
||||
[error]="[] | tuiFieldError | async"
|
||||
/>
|
||||
<tui-icon tuiPassword />
|
||||
</tui-textfield>
|
||||
<tui-error formControlName="password" />
|
||||
|
||||
<tui-textfield [style.margin-top.rem]="1">
|
||||
<label tuiLabel>{{ 'Confirm Password' | i18n }}</label>
|
||||
<input
|
||||
tuiTextfield
|
||||
type="password"
|
||||
formControlName="confirm"
|
||||
[tuiValidator]="
|
||||
form.controls.password.value || '' | tuiMapper: validator
|
||||
"
|
||||
/>
|
||||
<tui-icon tuiPassword />
|
||||
</tui-textfield>
|
||||
<tui-error
|
||||
<tui-textfield>
|
||||
<label tuiLabel>{{ 'Confirm Password' | i18n }}</label>
|
||||
<input
|
||||
tuiInput
|
||||
type="password"
|
||||
formControlName="confirm"
|
||||
[error]="[] | tuiFieldError | async"
|
||||
[tuiValidator]="
|
||||
form.controls.password.value || '' | tuiMapper: validator
|
||||
"
|
||||
/>
|
||||
<tui-icon tuiPassword />
|
||||
</tui-textfield>
|
||||
<tui-error formControlName="confirm" />
|
||||
|
||||
<footer>
|
||||
<footer>
|
||||
<button
|
||||
tuiButton
|
||||
size="m"
|
||||
[disabled]="
|
||||
isFresh
|
||||
? form.invalid
|
||||
: form.controls.password.value && form.invalid
|
||||
"
|
||||
>
|
||||
{{ 'Finish' | i18n }}
|
||||
</button>
|
||||
@if (!isFresh) {
|
||||
<button
|
||||
tuiButton
|
||||
[disabled]="
|
||||
isFresh
|
||||
? form.invalid
|
||||
: form.controls.password.value && form.invalid
|
||||
"
|
||||
size="m"
|
||||
appearance="secondary"
|
||||
type="button"
|
||||
(click)="skip()"
|
||||
>
|
||||
{{ 'Finish' | i18n }}
|
||||
{{ 'Skip' | i18n }}
|
||||
</button>
|
||||
@if (!isFresh) {
|
||||
<button
|
||||
tuiButton
|
||||
appearance="secondary"
|
||||
type="button"
|
||||
(click)="skip()"
|
||||
>
|
||||
{{ 'Skip' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</footer>
|
||||
</form>
|
||||
</section>
|
||||
`,
|
||||
styles: `
|
||||
.hostname-preview {
|
||||
color: var(--tui-text-secondary);
|
||||
font: var(--tui-font-text-s);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
}
|
||||
</footer>
|
||||
</form>
|
||||
`,
|
||||
imports: [
|
||||
AsyncPipe,
|
||||
ReactiveFormsModule,
|
||||
TuiCardLarge,
|
||||
TuiButton,
|
||||
TuiError,
|
||||
TuiAutoFocus,
|
||||
TuiFieldErrorPipe,
|
||||
TuiTextfield,
|
||||
TuiInput,
|
||||
TuiForm,
|
||||
TuiPassword,
|
||||
TuiValidator,
|
||||
TuiIcon,
|
||||
@@ -160,7 +132,7 @@ import { StateService } from '../services/state.service'
|
||||
})
|
||||
export default class PasswordPage {
|
||||
private readonly router = inject(Router)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly loader = inject(TuiNotificationMiddleService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly stateService = inject(StateService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiIcon,
|
||||
TuiLink,
|
||||
TuiLoader,
|
||||
TuiOptGroup,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiChevron } from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { ApiService } from '../services/api.service'
|
||||
@@ -18,22 +18,25 @@ import { StateService } from '../services/state.service'
|
||||
import { StartOSDiskInfoFull, StartOSDiskInfoWithId } from '../types'
|
||||
import { CIFS, CifsResult } from '../components/cifs.component'
|
||||
import { SELECT_NETWORK_BACKUP } from '../components/select-network-backup.dialog'
|
||||
import { UnlockPasswordDialog } from '../components/unlock-password.dialog'
|
||||
import { UNLOCK_PASSWORD } from '../components/unlock-password.dialog'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<section tuiCardLarge="compact">
|
||||
<header tuiHeader>
|
||||
<h2 tuiTitle>
|
||||
{{ 'Select Backup' | i18n }}
|
||||
<span tuiSubtitle>
|
||||
<hgroup tuiTitle>
|
||||
<h2>{{ 'Select Backup' | i18n }}</h2>
|
||||
<p tuiSubtitle>
|
||||
{{ 'Select the StartOS backup you want to restore' | i18n }}
|
||||
<a class="refresh" (click)="refresh()">
|
||||
<tui-icon icon="@tui.rotate-cw" />
|
||||
{{ 'Refresh' | i18n }}
|
||||
</a>
|
||||
</span>
|
||||
</h2>
|
||||
<button
|
||||
tuiLink
|
||||
appearance="action"
|
||||
iconEnd="@tui.rotate-cw"
|
||||
[textContent]="'Refresh' | i18n"
|
||||
(click)="refresh()"
|
||||
></button>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
|
||||
@if (loading) {
|
||||
@@ -41,82 +44,50 @@ import { UnlockPasswordDialog } from '../components/unlock-password.dialog'
|
||||
} @else {
|
||||
<button
|
||||
tuiButton
|
||||
iconEnd="@tui.chevron-down"
|
||||
[tuiDropdown]="dropdown"
|
||||
[tuiDropdownLimitWidth]="'fixed'"
|
||||
tuiChevron
|
||||
tuiDropdown
|
||||
tuiDropdownLimitWidth="fixed"
|
||||
[(tuiDropdownOpen)]="open"
|
||||
style="width: 100%"
|
||||
>
|
||||
{{ 'Select Backup' | i18n }}
|
||||
</button>
|
||||
|
||||
<ng-template #dropdown>
|
||||
<tui-data-list>
|
||||
<tui-opt-group>
|
||||
<button tuiOption new (click)="openCifs()">
|
||||
<tui-icon icon="@tui.folder-plus" />
|
||||
{{ 'Open Network Backup' | i18n }}
|
||||
</button>
|
||||
</tui-opt-group>
|
||||
<tui-data-list *tuiDropdown>
|
||||
<button tuiOption iconStart="@tui.folder-plus" (click)="openCifs()">
|
||||
{{ 'Open Network Backup' | i18n }}
|
||||
</button>
|
||||
<hr />
|
||||
<tui-opt-group [label]="'Physical Backups' | i18n">
|
||||
@for (server of physicalServers; track server.id) {
|
||||
<button tuiOption new (click)="selectPhysicalBackup(server)">
|
||||
<div class="server-item">
|
||||
<span>{{ server.id }}</span>
|
||||
<small>
|
||||
<button tuiOption (click)="selectPhysicalBackup(server)">
|
||||
<span tuiTitle>
|
||||
{{ server.id }}
|
||||
<span tuiSubtitle>
|
||||
{{ server.drive.vendor }} {{ server.drive.model }} ·
|
||||
{{ server.partition.logicalname }}
|
||||
</small>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
} @empty {
|
||||
<div class="no-items">{{ 'No physical backups' | i18n }}</div>
|
||||
<button tuiOption [disabled]="true">
|
||||
{{ 'No physical backups' | i18n }}
|
||||
</button>
|
||||
}
|
||||
</tui-opt-group>
|
||||
</tui-data-list>
|
||||
</ng-template>
|
||||
</button>
|
||||
}
|
||||
</section>
|
||||
`,
|
||||
styles: `
|
||||
.refresh {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
cursor: pointer;
|
||||
color: var(--tui-text-action);
|
||||
|
||||
tui-icon {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.server-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.no-items {
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: var(--tui-text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
`,
|
||||
imports: [
|
||||
TuiButton,
|
||||
TuiCardLarge,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiLoader,
|
||||
TuiIcon,
|
||||
TuiOptGroup,
|
||||
TuiTitle,
|
||||
TuiHeader,
|
||||
i18nPipe,
|
||||
TuiLink,
|
||||
TuiChevron,
|
||||
],
|
||||
})
|
||||
export default class RestorePage {
|
||||
@@ -142,10 +113,7 @@ export default class RestorePage {
|
||||
openCifs() {
|
||||
this.open = false
|
||||
this.dialogs
|
||||
.openComponent<CifsResult>(CIFS, {
|
||||
label: 'Connect Network Folder',
|
||||
size: 's',
|
||||
})
|
||||
.openComponent<CifsResult>(CIFS, { label: 'Connect Network Folder' })
|
||||
.subscribe(result => {
|
||||
if (result) {
|
||||
this.handleCifsResult(result)
|
||||
@@ -167,7 +135,7 @@ export default class RestorePage {
|
||||
type: 'cifs',
|
||||
...result.cifs,
|
||||
})
|
||||
} else if (result.servers.length > 1) {
|
||||
} else {
|
||||
this.showSelectNetworkBackupDialog(result.cifs, result.servers)
|
||||
}
|
||||
}
|
||||
@@ -178,8 +146,6 @@ export default class RestorePage {
|
||||
) {
|
||||
this.dialogs
|
||||
.openComponent<StartOSDiskInfoWithId | null>(SELECT_NETWORK_BACKUP, {
|
||||
label: 'Select Network Backup',
|
||||
size: 's',
|
||||
data: { servers },
|
||||
})
|
||||
.subscribe(server => {
|
||||
@@ -194,13 +160,7 @@ export default class RestorePage {
|
||||
target: { type: 'disk'; logicalname: string } | ({ type: 'cifs' } & T.Cifs),
|
||||
) {
|
||||
this.dialogs
|
||||
.openComponent<string | null>(
|
||||
new PolymorpheusComponent(UnlockPasswordDialog),
|
||||
{
|
||||
label: 'Unlock Backup',
|
||||
size: 's',
|
||||
},
|
||||
)
|
||||
.openComponent<string | null>(UNLOCK_PASSWORD)
|
||||
.subscribe(password => {
|
||||
if (password) {
|
||||
this.stateService.recoverySource = {
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
ErrorService,
|
||||
i18nPipe,
|
||||
} from '@start9labs/shared'
|
||||
import { TuiIcon, TuiLoader, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiIcon, TuiLoader, TuiTitle, TuiCell } from '@taiga-ui/core'
|
||||
import { TuiAvatar } from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiCell, TuiHeader } from '@taiga-ui/layout'
|
||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||
import { ApiService } from '../services/api.service'
|
||||
import { StateService } from '../services/state.service'
|
||||
import { DocumentationComponent } from '../components/documentation.component'
|
||||
@@ -50,7 +50,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
<!-- Step: Download Address Info (non-kiosk only) -->
|
||||
@if (!stateService.kiosk) {
|
||||
<button tuiCell="l" [disabled]="downloaded" (click)="download()">
|
||||
<tui-avatar appearance="secondary" src="@tui.download" />
|
||||
<span tuiAvatar="@tui.download" appearance="secondary"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Download Address Info' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
@@ -74,7 +74,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
[disabled]="(!stateService.kiosk && !downloaded) || usbRemoved"
|
||||
(click)="removeMedia()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.usb" />
|
||||
<span tuiAvatar="@tui.usb" appearance="secondary"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Remove Installation Media' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
@@ -96,7 +96,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
[disabled]="!usbRemoved || rebooted || rebooting"
|
||||
(click)="reboot()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.rotate-cw" />
|
||||
<span tuiAvatar="@tui.rotate-cw" appearance="secondary"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Restart Server' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
@@ -125,7 +125,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
[disabled]="!canOpenAddress"
|
||||
(click)="openLocalAddress()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.external-link" />
|
||||
<span tuiAvatar="@tui.external-link" appearance="secondary"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Open Local Address' | i18n }}
|
||||
<div tuiSubtitle>{{ lanAddress }}</div>
|
||||
@@ -143,7 +143,7 @@ import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
[disabled]="result.needsRestart && !rebooted"
|
||||
(click)="exitKiosk()"
|
||||
>
|
||||
<tui-avatar appearance="secondary" src="@tui.log-in" />
|
||||
<span tuiAvatar="@tui.log-in" appearance="secondary"></span>
|
||||
<div tuiTitle>
|
||||
{{ 'Continue to Login' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
@@ -233,9 +233,8 @@ export default class SuccessPage implements AfterViewInit {
|
||||
removeMedia() {
|
||||
this.dialogs
|
||||
.openComponent<boolean>(new PolymorpheusComponent(RemoveMediaDialog), {
|
||||
size: 's',
|
||||
dismissible: false,
|
||||
closeable: false,
|
||||
closable: false,
|
||||
})
|
||||
.subscribe(() => {
|
||||
this.usbRemoved = true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { Component, inject, OnInit } from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import {
|
||||
DialogService,
|
||||
@@ -11,10 +11,11 @@ import {
|
||||
TuiButton,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiIcon,
|
||||
TuiLink,
|
||||
TuiLoader,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiChevron } from '@taiga-ui/kit'
|
||||
import { TuiCardLarge, TuiHeader } from '@taiga-ui/layout'
|
||||
import { filter } from 'rxjs'
|
||||
import { ApiService } from '../services/api.service'
|
||||
@@ -24,18 +25,21 @@ import { StateService } from '../services/state.service'
|
||||
template: `
|
||||
<section tuiCardLarge="compact">
|
||||
<header tuiHeader>
|
||||
<h2 tuiTitle>
|
||||
{{ 'Transfer Data' | i18n }}
|
||||
<span tuiSubtitle>
|
||||
<hgroup tuiTitle>
|
||||
<h2>{{ 'Transfer Data' | i18n }}</h2>
|
||||
<p tuiSubtitle>
|
||||
{{
|
||||
'Select the drive containing your existing StartOS data' | i18n
|
||||
}}
|
||||
<a class="refresh" (click)="refresh()">
|
||||
<tui-icon icon="@tui.rotate-cw" />
|
||||
{{ 'Refresh' | i18n }}
|
||||
</a>
|
||||
</span>
|
||||
</h2>
|
||||
<button
|
||||
tuiLink
|
||||
appearance="action"
|
||||
iconEnd="@tui.rotate-cw"
|
||||
[textContent]="'Refresh' | i18n"
|
||||
(click)="refresh()"
|
||||
></button>
|
||||
</p>
|
||||
</hgroup>
|
||||
</header>
|
||||
|
||||
@if (loading) {
|
||||
@@ -43,75 +47,43 @@ import { StateService } from '../services/state.service'
|
||||
} @else {
|
||||
<button
|
||||
tuiButton
|
||||
iconEnd="@tui.chevron-down"
|
||||
[tuiDropdown]="dropdown"
|
||||
[tuiDropdownLimitWidth]="'fixed'"
|
||||
tuiChevron
|
||||
tuiDropdown
|
||||
tuiDropdownLimitWidth="fixed"
|
||||
[(tuiDropdownOpen)]="open"
|
||||
style="width: 100%"
|
||||
>
|
||||
{{ 'Select Drive' | i18n }}
|
||||
</button>
|
||||
|
||||
<ng-template #dropdown>
|
||||
<tui-data-list>
|
||||
<tui-data-list
|
||||
*tuiDropdown
|
||||
[emptyContent]="'No StartOS data drives found' | i18n"
|
||||
>
|
||||
@for (drive of drives; track drive.logicalname) {
|
||||
<button tuiOption new (click)="select(drive)">
|
||||
<div class="drive-item">
|
||||
<span>{{ drive.vendor }} {{ drive.model }}</span>
|
||||
<small>{{ drive.logicalname }}</small>
|
||||
</div>
|
||||
<button tuiOption (click)="select(drive)">
|
||||
<span tuiTitle>
|
||||
{{ drive.vendor }} {{ drive.model }}
|
||||
<span tuiSubtitle>{{ drive.logicalname }}</span>
|
||||
</span>
|
||||
</button>
|
||||
} @empty {
|
||||
<div class="no-items">
|
||||
{{ 'No StartOS data drives found' | i18n }}
|
||||
</div>
|
||||
}
|
||||
</tui-data-list>
|
||||
</ng-template>
|
||||
</button>
|
||||
}
|
||||
</section>
|
||||
`,
|
||||
styles: `
|
||||
.refresh {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
cursor: pointer;
|
||||
color: var(--tui-text-action);
|
||||
|
||||
tui-icon {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
.drive-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
small {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.no-items {
|
||||
padding: 0.5rem 0.75rem;
|
||||
color: var(--tui-text-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
`,
|
||||
imports: [
|
||||
TuiButton,
|
||||
TuiCardLarge,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiIcon,
|
||||
TuiLink,
|
||||
TuiChevron,
|
||||
TuiLoader,
|
||||
TuiTitle,
|
||||
TuiHeader,
|
||||
i18nPipe,
|
||||
],
|
||||
})
|
||||
export default class TransferPage {
|
||||
export default class TransferPage implements OnInit {
|
||||
private readonly api = inject(ApiService)
|
||||
private readonly router = inject(Router)
|
||||
private readonly dialogs = inject(DialogService)
|
||||
@@ -137,7 +109,6 @@ export default class TransferPage {
|
||||
this.dialogs
|
||||
.openConfirm({
|
||||
label: 'Warning',
|
||||
size: 's',
|
||||
data: {
|
||||
content:
|
||||
'After transferring data from this drive, do not attempt to boot into it again as a Start9 Server. This may result in services malfunctioning, data corruption, or loss of funds.',
|
||||
|
||||
@@ -12,6 +12,10 @@ tui-root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-inline-start: 1rem;
|
||||
}
|
||||
|
||||
router-outlet + * {
|
||||
height: 100%;
|
||||
max-width: min(35rem, 100vw);
|
||||
@@ -30,41 +34,11 @@ router-outlet + * {
|
||||
}
|
||||
}
|
||||
|
||||
.inline-title {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
:first-child {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: var(--tui-disabled-opacity);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
font: var(--tui-font-heading-4);
|
||||
|
||||
p {
|
||||
font: var(--tui-font-text-m);
|
||||
color: var(--tui-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font: var(--tui-font-heading-6);
|
||||
}
|
||||
|
||||
.g-positive {
|
||||
color: var(--tui-status-positive);
|
||||
}
|
||||
@@ -77,14 +51,27 @@ h2 {
|
||||
color: var(--tui-status-negative);
|
||||
}
|
||||
|
||||
.g-secondary {
|
||||
color: var(--tui-text-secondary) !important;
|
||||
}
|
||||
|
||||
.g-info {
|
||||
color: var(--tui-status-info);
|
||||
}
|
||||
|
||||
[tuiCardLarge] footer button {
|
||||
width: 100%;
|
||||
[tuiCardLarge] footer {
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
[tuiCell]:not(:last-of-type) {
|
||||
[tuiCell]:not([tuiOption]):not(:last-of-type) {
|
||||
box-shadow: 0 calc(0.5rem + 1px) 0 -0.5rem var(--tui-border-normal);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove in Taiga v5.0
|
||||
[tuiButton] {
|
||||
min-block-size: var(--t-size);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user