Merge branch 'next/major' of github.com:Start9Labs/start-os into bugfix/alpha.20

This commit is contained in:
Matt Hill
2026-03-15 16:17:02 -06:00
184 changed files with 1898 additions and 2330 deletions

View File

@@ -1,4 +1,4 @@
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
:host {
margin: 0;

View File

@@ -15,12 +15,9 @@ import { MarketplacePkgBase } from '../../../types'
selector: 'marketplace-dep-item',
template: `
<div class="outer-container">
<tui-avatar
appearance="action-grayscale"
class="dep-img"
size="l"
[src]="getImage(dep.key)"
/>
<span tuiAvatar appearance="action-grayscale" class="dep-img" size="l">
<img alt="" [src]="getImage(dep.key)" />
</span>
<div>
<tui-line-clamp [linesLimit]="2" [content]="titleContent" />
<ng-template #titleContent>
@@ -107,8 +104,10 @@ export class MarketplaceDepItemComponent {
dep!: KeyValue<string, T.DependencyMetadata>
getImage(key: string) {
const icon = this.pkg.dependencyMetadata[key]?.icon
return icon ? icon : 'assets/img/service-icons/fallback.png'
return (
this.pkg.dependencyMetadata[key]?.icon ||
'assets/img/service-icons/fallback.png'
)
}
getTitle(key: string): string {

View File

@@ -1,9 +1,8 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { RouterLink } from '@angular/router'
import { i18nPipe, TrustUrlPipe } from '@start9labs/shared'
import { TuiTitle } from '@taiga-ui/core'
import { TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiAvatar } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { MarketplacePkg } from '../../types'
@Component({
@@ -21,10 +20,9 @@ import { MarketplacePkg } from '../../types'
[queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
queryParamsHandling="merge"
>
<tui-avatar
appearance="action-grayscale"
[src]="pkg.icon | trustUrl"
/>
<span tuiAvatar appearance="action-grayscale">
<img alt="" [src]="pkg.icon | trustUrl" />
</span>
<span tuiTitle>
{{ pkg.title }}
<span tuiSubtitle>{{ pkg.version }}</span>

View File

@@ -4,7 +4,7 @@ import {
inject,
Input,
} from '@angular/core'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { TuiButton, TuiDialogContext, TuiDialogService } from '@taiga-ui/core'
import { TuiCarousel } from '@taiga-ui/kit'
import { PolymorpheusContent } from '@taiga-ui/polymorpheus'
@@ -130,7 +130,7 @@ export class MarketplacePackageScreenshotComponent {
index = 0
isMobile = inject(TUI_IS_MOBILE)
isMobile = inject(WA_IS_MOBILE)
presentModalImg(content: PolymorpheusContent<TuiDialogContext>) {
this.dialogs

View File

@@ -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 {

View File

@@ -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(),

View File

@@ -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()
}

View File

@@ -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,
)

View File

@@ -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;
}
`,
})

View File

@@ -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)

View File

@@ -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',
@@ -398,10 +371,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: () => {

View File

@@ -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)

View File

@@ -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,

View File

@@ -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 =

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 = {

View File

@@ -13,9 +13,9 @@ import {
i18nPipe,
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiIcon, TuiLoader, TuiTitle } from '@taiga-ui/core'
import { TuiCell, TuiIcon, TuiLoader, TuiTitle } 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 { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { DocumentationComponent } from '../components/documentation.component'
import { MatrixComponent } from '../components/matrix.component'
@@ -51,7 +51,7 @@ import { StateService } from '../services/state.service'
<!-- Step: Download Address Info (non-kiosk only) -->
@if (!stateService.kiosk) {
<button tuiCell="l" (click)="download()">
<tui-avatar appearance="secondary" src="@tui.download" />
<span tuiAvatar="@tui.download" appearance="secondary"></span>
<div tuiTitle>
{{ 'Download Address Info' | i18n }}
<div tuiSubtitle>
@@ -75,7 +75,7 @@ import { StateService } from '../services/state.service'
[disabled]="!stateService.kiosk && !downloaded"
(click)="removeMedia()"
>
<tui-avatar appearance="secondary" src="@tui.usb" />
<span tuiAvatar="@tui.usb" appearance="secondary"></span>
<div tuiTitle>
{{ 'Remove Installation Media' | i18n }}
<div tuiSubtitle>
@@ -98,7 +98,7 @@ import { StateService } from '../services/state.service'
[disabled]="!usbRemoved"
(click)="acknowledgeMok()"
>
<tui-avatar appearance="secondary" src="@tui.shield-check" />
<span tuiAvatar="@tui.shield-check" appearance="secondary"></span>
<div tuiTitle>
{{ 'Secure Boot Enrollment' | i18n }}
<div tuiSubtitle>
@@ -125,7 +125,7 @@ import { StateService } from '../services/state.service'
"
(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>
@@ -146,7 +146,7 @@ import { StateService } from '../services/state.service'
</button>
} @else if (stateService.kiosk) {
<button tuiCell="l" (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>
@@ -164,7 +164,7 @@ import { StateService } from '../services/state.service'
[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>
@@ -173,8 +173,6 @@ import { StateService } from '../services/state.service'
<app-documentation hidden [lanAddress]="lanAddress" />
}
<!-- Step: Continue to Login (kiosk only) -->
}
</section>
`,
@@ -255,9 +253,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
@@ -270,7 +267,7 @@ export default class SuccessPage implements AfterViewInit {
label: 'Secure Boot',
size: 'm',
dismissible: false,
closeable: false,
closable: false,
})
.subscribe(() => {
this.mokAcknowledged = true

View File

@@ -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.',

View File

@@ -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);
}

View File

@@ -11,7 +11,12 @@ import { getErrorMessage } from '../services/error.service'
@Component({
template: `
@if (error()) {
<tui-notification appearance="negative" safeLinks [innerHTML]="error()" />
<div
tuiNotification
appearance="negative"
safeLinks
[innerHTML]="error()"
></div>
}
@if (content(); as result) {

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { TuiAutoFocus } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext, TuiTextfield } from '@taiga-ui/core'
import { TuiButton, TuiDialogContext, TuiInput } from '@taiga-ui/core'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { i18nPipe } from '../i18n/i18n.pipe'
import { i18nKey } from '../i18n/i18n.providers'
@@ -23,7 +23,7 @@ import { i18nKey } from '../i18n/i18n.providers'
</label>
}
<input
tuiTextfield
tuiInput
tuiAutoFocus
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="value"
@@ -81,7 +81,7 @@ import { i18nKey } from '../i18n/i18n.providers'
-webkit-text-security: disc;
}
`,
imports: [FormsModule, TuiButton, TuiTextfield, TuiAutoFocus, i18nPipe],
imports: [FormsModule, TuiButton, TuiInput, TuiAutoFocus, i18nPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PromptModal {

View File

@@ -1,7 +1,6 @@
import { DatePipe } from '@angular/common'
import { Component, input } from '@angular/core'
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { TuiIcon, TuiTitle, TuiCell } from '@taiga-ui/core'
import { StartOSDiskInfo } from '../types/api'
@Component({

View File

@@ -5,6 +5,7 @@ import {
InjectionToken,
input,
} from '@angular/core'
import { tuiSetSignal } from '@taiga-ui/cdk'
import { TuiHintDirective } from '@taiga-ui/core'
import { i18nPipe } from '../i18n/i18n.pipe'
@@ -33,7 +34,8 @@ export class DocsLinkDirective {
})
constructor() {
inject(TuiHintDirective).content.set(
tuiSetSignal(
inject(TuiHintDirective).content,
inject(i18nPipe).transform('Documentation'),
)
}

View File

@@ -32,7 +32,6 @@ export * from './services/download-html.service'
export * from './services/exver.service'
export * from './services/error.service'
export * from './services/http.service'
export * from './services/loading.service'
export * from './services/setup-logs.service'
export * from './types/api'

View File

@@ -1,6 +1,6 @@
import { inject, Injectable } from '@angular/core'
import { Clipboard } from '@angular/cdk/clipboard'
import { TuiAlertService } from '@taiga-ui/core'
import { TuiNotificationService } from '@taiga-ui/core'
import { i18nPipe } from '../i18n/i18n.pipe'
@@ -8,7 +8,7 @@ import { i18nPipe } from '../i18n/i18n.pipe'
export class CopyService {
private readonly clipboard = inject(Clipboard)
private readonly i18n = inject(i18nPipe)
private readonly alerts = inject(TuiAlertService)
private readonly alerts = inject(TuiNotificationService)
async copy(text: string) {
const success = this.clipboard.copy(text)

View File

@@ -1,12 +1,12 @@
import { ErrorHandler, inject, Injectable } from '@angular/core'
import { TuiAlertService } from '@taiga-ui/core'
import { TuiNotificationService } from '@taiga-ui/core'
import { HttpError } from '../classes/http-error'
@Injectable({
providedIn: 'root',
})
export class ErrorService extends ErrorHandler {
private readonly alerts = inject(TuiAlertService)
private readonly alerts = inject(TuiNotificationService)
override handleError(error: HttpError | string, link?: string) {
console.error(error)

View File

@@ -1,44 +0,0 @@
import { ChangeDetectionStrategy, Component, Injectable } from '@angular/core'
import { TuiPopoverService } from '@taiga-ui/cdk'
import { TUI_DIALOGS, TuiLoader } from '@taiga-ui/core'
import { injectContext } from '@taiga-ui/polymorpheus'
import { i18nPipe } from '../i18n/i18n.pipe'
import { i18nKey } from '../i18n/i18n.providers'
@Component({
template: '<tui-loader [textContent]="content | i18n" />',
styles: `
:host {
display: flex;
align-items: center;
max-width: 80%;
margin: auto;
padding: 1.5rem;
background: var(--tui-background-elevation-1);
border-radius: var(--tui-radius-m);
box-shadow: var(--tui-shadow-popup);
--tui-background-accent-1: var(--tui-status-warning);
}
tui-loader {
flex-shrink: 0;
min-width: 2rem;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiLoader, i18nPipe],
})
class LoadingComponent {
readonly content = injectContext<{ content: i18nKey }>().content
}
@Injectable({
providedIn: `root`,
useFactory: () => new LoadingService(TUI_DIALOGS, LoadingComponent),
})
export class LoadingService extends TuiPopoverService<unknown> {
override open<G = void>(textContent: i18nKey | '' = '') {
return super.open<G>(textContent)
}
}

View File

@@ -95,9 +95,6 @@ $wide-modal: 900px;
--tw-color-zinc-800: 39 39 42;
--tw-color-zinc-900: 24 24 27;
--tw-color-zinc-950: 9 9 11;
--tui-font-text: 'Proxima Nova', system-ui;
--tui-font-heading: 'Proxima Nova', system-ui;
}
body {

View File

@@ -1,4 +1,4 @@
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
[tuiTheme='dark'] {
--tui-background-base: rgba(23, 23, 23, 1);
@@ -135,13 +135,13 @@ tui-dropdown[data-appearance='start-os'][data-appearance='start-os'] {
inset 0 0 1rem rgba(0, 0, 0, 0.25),
var(--tui-shadow-medium);
[tuiOption] {
justify-content: flex-start;
gap: 0.5rem;
input {
// Checkmark
--tui-text-action: var(--tui-text-primary);
}
}
// @TODO Alex: Move to Taiga UI
// TODO: Remove in Taiga v5.0
a[tuiIconButton]:not([href]) {
pointer-events: none;
opacity: var(--tui-disabled-opacity);
@@ -160,7 +160,7 @@ tui-textfield [tuiTooltip] {
padding-block: 0.75rem;
[tuiTitle] {
font: var(--tui-font-text-l);
font: var(--tui-typography-body-l);
}
}
@@ -169,7 +169,16 @@ tui-textfield [tuiTooltip] {
}
}
// TODO: Remove after migrating to v5
[tuiOption] {
word-break: break-word;
:root {
--tui-typography-family-text: 'Proxima Nova', system-ui;
--tui-typography-family-display: 'Proxima Nova', system-ui;
}
tui-notification-middle {
--tui-background-accent-1: var(--tui-status-warning);
}
// TODO: Remove in Taiga v5.0
[tuiButton] {
min-block-size: var(--t-size);
}

View File

@@ -1,13 +1,14 @@
import { tuiDropdownOptionsProvider } from '@taiga-ui/core'
import { provideEventPlugins } from '@taiga-ui/event-plugins'
import { provideAnimations } from '@angular/platform-browser/animations'
import {
tuiDropdownOptionsProvider,
tuiDialogOptionsProvider,
provideTaiga,
} from '@taiga-ui/core'
import {
ApplicationConfig,
provideBrowserGlobalErrorListeners,
provideZonelessChangeDetection,
} from '@angular/core'
import { provideRouter, withRouterConfig } from '@angular/router'
import { tuiDialogOptionsProvider } from '@taiga-ui/experimental'
import { PatchDB } from 'patch-db-client'
import {
PATCH_CACHE,
@@ -31,11 +32,10 @@ const {
export const appConfig: ApplicationConfig = {
providers: [
provideAnimations(),
provideBrowserGlobalErrorListeners(),
provideZonelessChangeDetection(),
provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' })),
provideEventPlugins(),
provideTaiga({ mode: 'dark' }),
tuiDropdownOptionsProvider({ appearance: 'start-9' }),
tuiDialogOptionsProvider({ appearance: 'start-9 taiga' }),
{

View File

@@ -29,7 +29,7 @@ import { SidebarService } from 'src/app/services/sidebar.service'
}
h1 {
font: var(--tui-font-heading-6);
font: var(--tui-typography-heading-h6);
margin-inline-end: auto;
}

View File

@@ -1,31 +1,25 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import {
NonNullableFormBuilder,
ReactiveFormsModule,
Validators,
} from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { ErrorService } from '@start9labs/shared'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import {
TUI_IS_MOBILE,
TuiAnimated,
TuiAutoFocus,
tuiMarkControlAsTouchedAndValidate,
} from '@taiga-ui/cdk'
import {
TuiButton,
TuiDialogContext,
TuiError,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { TuiButton, TuiDialogContext, TuiError, TuiInput } from '@taiga-ui/core'
import {
TuiChevron,
TuiDataListWrapper,
TuiElasticContainer,
TuiFieldErrorPipe,
TuiNotificationMiddleService,
TuiSelect,
} from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout'
import { TuiElasticContainer, TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from 'src/app/services/api/api.service'
@@ -43,9 +37,9 @@ import {
<form tuiForm [formGroup]="form">
<tui-textfield>
<label tuiLabel>Name</label>
<input tuiTextfield tuiAutoFocus formControlName="name" />
<input tuiInput tuiAutoFocus formControlName="name" />
</tui-textfield>
<tui-error formControlName="name" [error]="[] | tuiFieldError | async" />
<tui-error formControlName="name" />
@if (!context.data.device) {
<tui-textfield tuiChevron [stringify]="stringify">
@@ -62,31 +56,24 @@ import {
}
@if (!mobile) {
<tui-data-list-wrapper
*tuiTextfieldDropdown
new
*tuiDropdown
[items]="context.data.subnets()"
(itemClick)="onSubnet($event)"
/>
}
</tui-textfield>
<tui-error
formControlName="subnet"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="subnet" />
<tui-elastic-container>
@if (form.controls.subnet.value?.range) {
<tui-textfield>
<tui-textfield tuiAnimated>
<label tuiLabel>LAN IP</label>
<input tuiTextfield tuiAutoFocus formControlName="ip" />
<input tuiInput tuiAutoFocus formControlName="ip" />
</tui-textfield>
}
</tui-elastic-container>
@if (form.controls.subnet.value?.range) {
<tui-error
formControlName="ip"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="ip" />
}
}
<footer>
@@ -96,27 +83,26 @@ import {
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiAutoFocus,
TuiButton,
TuiDataListWrapper,
TuiError,
TuiFieldErrorPipe,
TuiForm,
TuiSelect,
TuiTextfield,
TuiInput,
TuiAnimated,
TuiChevron,
TuiElasticContainer,
],
})
export class DevicesAdd {
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService)
private readonly dialogs = inject(TuiDialogService)
private readonly dialogs = inject(TuiResponsiveDialogService)
protected readonly mobile = inject(TUI_IS_MOBILE)
protected readonly mobile = inject(WA_IS_MOBILE)
protected readonly context =
injectContext<TuiDialogContext<void, DeviceData>>()
@@ -167,7 +153,7 @@ export class DevicesAdd {
return
}
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
const { ip, name, subnet } = this.form.getRawValue()
const data = { ip, name, subnet: subnet?.range || '' }

View File

@@ -1,11 +1,5 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import {
TuiButton,
TuiDialogContext,
TuiIcon,
TuiTextfield,
TuiTitle,
} from '@taiga-ui/core'
import { TuiButton, TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TuiCopy, TuiSegmented, TuiTextarea } from '@taiga-ui/kit'
import { TuiHeader } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
@@ -60,7 +54,6 @@ import { QrCodeComponent } from 'ng-qrcode'
TuiIcon,
TuiTitle,
TuiSegmented,
TuiTextfield,
TuiTextarea,
TuiCopy,
],

View File

@@ -6,15 +6,10 @@ import {
Signal,
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { ErrorService, LoadingService } from '@start9labs/shared'
import {
TuiButton,
TuiDataList,
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { ErrorService } from '@start9labs/shared'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
import { TUI_CONFIRM, TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter, map } from 'rxjs'
import { ApiService } from 'src/app/services/api/api.service'
@@ -49,16 +44,15 @@ import { MappedDevice, MappedSubnet } from './utils'
tuiIconButton
size="xs"
tuiDropdown
tuiDropdownOpen
tuiDropdownAuto
appearance="flat-grayscale"
iconStart="@tui.ellipsis-vertical"
>
Actions
<tui-data-list *tuiTextfieldDropdown size="s">
<tui-data-list *tuiDropdown size="s">
<button
tuiOption
iconStart="@tui.pencil"
new
(click)="onEdit(device)"
>
Rename
@@ -66,7 +60,6 @@ import { MappedDevice, MappedSubnet } from './utils'
<button
tuiOption
iconStart="@tui.settings"
new
(click)="onConfig(device)"
>
View Config
@@ -74,7 +67,6 @@ import { MappedDevice, MappedSubnet } from './utils'
<button
tuiOption
iconStart="@tui.trash"
new
(click)="onDelete(device)"
>
Delete
@@ -90,12 +82,12 @@ import { MappedDevice, MappedSubnet } from './utils'
</table>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiButton, TuiDropdown, TuiDataList, TuiTextfield],
imports: [TuiButton, TuiDropdown, TuiDataList],
})
export default class Devices {
private readonly dialogs = inject(TuiDialogService)
private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
protected readonly subnets: Signal<readonly MappedSubnet[]> = toSignal(
@@ -145,7 +137,7 @@ export default class Devices {
}
async onConfig({ subnet, ip }: MappedDevice) {
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
try {
const data = await this.api.showDeviceConfig({ subnet: subnet.range, ip })
@@ -163,7 +155,7 @@ export default class Devices {
.open(TUI_CONFIRM, { label: 'Are you sure?' })
.pipe(filter(Boolean))
.subscribe(async () => {
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
try {
await this.api.deleteDevice({ subnet: subnet.range, ip })
} catch (e: any) {

View File

@@ -1,33 +1,30 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import {
NonNullableFormBuilder,
ReactiveFormsModule,
Validators,
} from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { ErrorService } from '@start9labs/shared'
import {
TUI_IS_MOBILE,
tuiMarkControlAsTouchedAndValidate,
TuiValueChanges,
} from '@taiga-ui/cdk'
import {
TuiButton,
TuiCheckbox,
TuiDialogContext,
TuiError,
TuiNumberFormat,
TuiTextfield,
} from '@taiga-ui/core'
import {
TuiCheckbox,
TuiChevron,
TuiDataListWrapper,
TuiElasticContainer,
TuiFieldErrorPipe,
TuiInputNumber,
TuiNotificationMiddleService,
TuiSelect,
} from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout'
import { TuiElasticContainer, TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from 'src/app/services/api/api.service'
@@ -40,7 +37,7 @@ import { MappedDevice, PortForwardsData } from './utils'
<label tuiLabel>Label</label>
<input tuiTextfield formControlName="label" />
</tui-textfield>
<tui-error formControlName="label" [error]="[] | tuiFieldError | async" />
<tui-error formControlName="label" />
<tui-textfield tuiChevron>
<label tuiLabel>External IP</label>
@if (mobile) {
@@ -54,17 +51,10 @@ import { MappedDevice, PortForwardsData } from './utils'
<input tuiSelect formControlName="externalip" />
}
@if (!mobile) {
<tui-data-list-wrapper
*tuiTextfieldDropdown
new
[items]="context.data.ips()"
/>
<tui-data-list-wrapper *tuiDropdown [items]="context.data.ips()" />
}
</tui-textfield>
<tui-error
formControlName="externalip"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="externalip" />
<tui-textfield>
<label tuiLabel>External Port</label>
<input
@@ -76,10 +66,7 @@ import { MappedDevice, PortForwardsData } from './utils'
(tuiValueChanges)="checkShow80()"
/>
</tui-textfield>
<tui-error
formControlName="externalport"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="externalport" />
<tui-textfield tuiChevron [stringify]="stringify">
<label tuiLabel>Device</label>
@if (mobile) {
@@ -94,16 +81,12 @@ import { MappedDevice, PortForwardsData } from './utils'
}
@if (!mobile) {
<tui-data-list-wrapper
*tuiTextfieldDropdown
new
*tuiDropdown
[items]="context.data.devices()"
/>
}
</tui-textfield>
<tui-error
formControlName="device"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="device" />
<tui-textfield>
<label tuiLabel>Internal Port</label>
<input
@@ -115,10 +98,7 @@ import { MappedDevice, PortForwardsData } from './utils'
(tuiValueChanges)="checkShow80()"
/>
</tui-textfield>
<tui-error
formControlName="internalport"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="internalport" />
<tui-elastic-container>
@if (show80) {
<label tuiLabel>
@@ -137,7 +117,6 @@ import { MappedDevice, PortForwardsData } from './utils'
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiButton,
TuiChevron,
@@ -145,8 +124,6 @@ import { MappedDevice, PortForwardsData } from './utils'
TuiError,
TuiInputNumber,
TuiNumberFormat,
TuiFieldErrorPipe,
TuiTextfield,
TuiSelect,
TuiForm,
TuiCheckbox,
@@ -156,12 +133,12 @@ import { MappedDevice, PortForwardsData } from './utils'
})
export class PortForwardsAdd {
private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
show80 = false
protected readonly mobile = inject(TUI_IS_MOBILE)
protected readonly mobile = inject(WA_IS_MOBILE)
protected readonly context =
injectContext<TuiDialogContext<void, PortForwardsData>>()
@@ -194,7 +171,7 @@ export class PortForwardsAdd {
return
}
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
const { label, externalip, externalport, device, internalport, also80 } =
this.form.getRawValue()

View File

@@ -1,21 +1,20 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import {
NonNullableFormBuilder,
ReactiveFormsModule,
Validators,
} from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { ErrorService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import {
TuiButton,
TuiDialogContext,
TuiError,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiFieldErrorPipe } from '@taiga-ui/kit'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/api.service'
export interface EditLabelData {
@@ -30,7 +29,7 @@ export interface EditLabelData {
<label tuiLabel>Label</label>
<input tuiTextfield formControlName="label" />
</tui-textfield>
<tui-error formControlName="label" [error]="[] | tuiFieldError | async" />
<tui-error formControlName="label" />
<footer>
<button tuiButton [disabled]="form.invalid" (click)="onSave()">
Save
@@ -39,19 +38,11 @@ export interface EditLabelData {
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiButton,
TuiError,
TuiFieldErrorPipe,
TuiTextfield,
TuiForm,
],
imports: [ReactiveFormsModule, TuiButton, TuiError, TuiTextfield, TuiForm],
})
export class PortForwardsEditLabel {
private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
protected readonly context =
@@ -62,7 +53,7 @@ export class PortForwardsEditLabel {
})
protected async onSave() {
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
try {
await this.api.updateForwardLabel({

View File

@@ -8,8 +8,9 @@ import {
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { ErrorService } from '@start9labs/shared'
import { utils } from '@start9labs/start-sdk'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import {
TuiButton,
TuiDataList,
@@ -17,8 +18,11 @@ import {
TuiLoader,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { TUI_CONFIRM, TuiSwitch } from '@taiga-ui/kit'
import {
TUI_CONFIRM,
TuiNotificationMiddleService,
TuiSwitch,
} from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter, map } from 'rxjs'
import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add'
@@ -51,7 +55,7 @@ import { MappedDevice, MappedForward } from './utils'
<tr>
<td>
<tui-loader
[showLoader]="toggling() === $index"
[loading]="toggling() === $index"
size="xs"
[overlay]="true"
>
@@ -75,12 +79,12 @@ import { MappedDevice, MappedForward } from './utils'
tuiIconButton
size="xs"
tuiDropdown
tuiDropdownOpen
tuiDropdownAuto
appearance="flat-grayscale"
iconStart="@tui.ellipsis-vertical"
>
Actions
<tui-data-list *tuiTextfieldDropdown size="s">
<tui-data-list *tuiDropdown size="s">
<button
tuiOption
iconStart="@tui.pencil"
@@ -119,9 +123,9 @@ import { MappedDevice, MappedForward } from './utils'
],
})
export default class PortForwards {
private readonly dialogs = inject(TuiDialogService)
private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
private readonly patch = inject<PatchDB<TunnelData>>(PatchDB)
private readonly errorService = inject(ErrorService)
@@ -210,7 +214,7 @@ export default class PortForwards {
.open(TUI_CONFIRM, { label: 'Are you sure?' })
.pipe(filter(Boolean))
.subscribe(async () => {
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
const source = `${externalip}:${externalport}`
try {

View File

@@ -1,4 +1,3 @@
import { AsyncPipe } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
@@ -15,17 +14,14 @@ import {
import { ErrorService } from '@start9labs/shared'
import { TuiAutoFocus, TuiValidator } from '@taiga-ui/cdk'
import {
TuiAlertService,
TuiButton,
TuiDialogContext,
TuiError,
TuiTextfield,
} from '@taiga-ui/core'
import {
TuiButtonLoading,
TuiFieldErrorPipe,
TuiNotificationService,
TuiInput,
tuiValidationErrorsProvider,
} from '@taiga-ui/kit'
} from '@taiga-ui/core'
import { TuiButtonLoading } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { map } from 'rxjs'
@@ -36,24 +32,18 @@ import { ApiService } from 'src/app/services/api/api.service'
<form tuiForm [formGroup]="form">
<tui-textfield>
<label tuiLabel>New password</label>
<input tuiTextfield tuiAutoFocus formControlName="password" />
<input tuiInput tuiAutoFocus formControlName="password" />
</tui-textfield>
<tui-error
formControlName="password"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="password" />
<tui-textfield>
<label tuiLabel>Confirm new password</label>
<input
tuiTextfield
tuiInput
formControlName="confirm"
[tuiValidator]="matchValidator()"
/>
</tui-textfield>
<tui-error
formControlName="confirm"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="confirm" />
<footer>
<button
tuiButton
@@ -76,22 +66,20 @@ import { ApiService } from 'src/app/services/api/api.service'
],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiAutoFocus,
TuiButton,
TuiButtonLoading,
TuiError,
TuiFieldErrorPipe,
TuiForm,
TuiTextfield,
TuiInput,
TuiValidator,
],
})
export class ChangePasswordDialog {
private readonly context = injectContext<TuiDialogContext<void>>()
private readonly api = inject(ApiService)
private readonly alerts = inject(TuiAlertService)
private readonly alerts = inject(TuiNotificationService)
private readonly errorService = inject(ErrorService)
protected readonly loading = signal(false)

View File

@@ -5,28 +5,31 @@ import {
signal,
} from '@angular/core'
import { Router } from '@angular/router'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiAppearance, TuiButton, TuiTitle } from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit'
import { TuiCard, TuiCell } from '@taiga-ui/layout'
import { ErrorService } from '@start9labs/shared'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { TuiButton, TuiCell, TuiTitle } from '@taiga-ui/core'
import {
TuiBadge,
TuiButtonLoading,
TuiNotificationMiddleService,
} from '@taiga-ui/kit'
import { TuiCard } from '@taiga-ui/layout'
import { ApiService } from 'src/app/services/api/api.service'
import { AuthService } from 'src/app/services/auth.service'
import { UpdateService } from 'src/app/services/update.service'
import { CHANGE_PASSWORD } from './change-password'
@Component({
template: `
<div tuiCardLarge tuiAppearance="neutral">
<div tuiCardLarge>
<div tuiCell>
<span tuiTitle>
<strong>
Version
@if (update.hasUpdate()) {
<tui-badge appearance="positive" size="s">
<span tuiBadge appearance="positive" size="s">
Update Available
</tui-badge>
</span>
}
</strong>
<span tuiSubtitle>Current: {{ update.installed() ?? '—' }}</span>
@@ -69,24 +72,25 @@ import { CHANGE_PASSWORD } from './change-password'
</div>
</div>
`,
styles: `
[tuiCardLarge] {
background: var(--tui-background-neutral-1);
&:not([data-appearance]) {
display: none;
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
TuiCard,
TuiCell,
TuiTitle,
TuiButton,
TuiButtonLoading,
TuiBadge,
TuiAppearance,
],
imports: [TuiCard, TuiCell, TuiTitle, TuiButton, TuiButtonLoading, TuiBadge],
})
export default class Settings {
private readonly dialogs = inject(TuiDialogService)
private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService)
private readonly auth = inject(AuthService)
private readonly router = inject(Router)
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
protected readonly update = inject(UpdateService)
protected readonly checking = signal(false)
@@ -121,7 +125,7 @@ export default class Settings {
}
protected async onLogout() {
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
try {
await this.api.logout()

View File

@@ -1,19 +1,12 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import {
NonNullableFormBuilder,
ReactiveFormsModule,
Validators,
} from '@angular/forms'
import { LoadingService } from '@start9labs/shared'
import { TuiAutoFocus, tuiMarkControlAsTouchedAndValidate } from '@taiga-ui/cdk'
import {
TuiButton,
TuiDialogContext,
TuiError,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiFieldErrorPipe } from '@taiga-ui/kit'
import { TuiButton, TuiDialogContext, TuiError, TuiInput } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { TuiForm } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { ApiService } from 'src/app/services/api/api.service'
@@ -23,18 +16,15 @@ import { ApiService } from 'src/app/services/api/api.service'
<form tuiForm [formGroup]="form">
<tui-textfield>
<label tuiLabel>Name</label>
<input tuiTextfield tuiAutoFocus formControlName="name" />
<input tuiInput tuiAutoFocus formControlName="name" />
</tui-textfield>
<tui-error formControlName="name" [error]="[] | tuiFieldError | async" />
<tui-error formControlName="name" />
@if (!context.data.name) {
<tui-textfield>
<label tuiLabel>IP Range</label>
<input tuiTextfield formControlName="subnet" />
<input tuiInput formControlName="subnet" />
</tui-textfield>
<tui-error
formControlName="subnet"
[error]="[] | tuiFieldError | async"
/>
<tui-error formControlName="subnet" />
}
<footer>
<button tuiButton (click)="onSave()">Save</button>
@@ -43,19 +33,17 @@ import { ApiService } from 'src/app/services/api/api.service'
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiAutoFocus,
TuiButton,
TuiError,
TuiFieldErrorPipe,
TuiForm,
TuiTextfield,
TuiInput,
],
})
export class SubnetsAdd {
private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
protected readonly context = injectContext<TuiDialogContext<void, Data>>()
protected readonly form = inject(NonNullableFormBuilder).group({
@@ -78,7 +66,7 @@ export class SubnetsAdd {
return
}
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
const value = this.form.getRawValue()
try {

View File

@@ -1,15 +1,9 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { LoadingService } from '@start9labs/shared'
import { utils } from '@start9labs/start-sdk'
import {
TuiButton,
TuiDataList,
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { TUI_CONFIRM } from '@taiga-ui/kit'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { TuiButton, TuiDataList, TuiDropdown } from '@taiga-ui/core'
import { TUI_CONFIRM, TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter, map } from 'rxjs'
import { ApiService } from 'src/app/services/api/api.service'
@@ -41,16 +35,15 @@ import { SUBNETS_ADD } from './add'
tuiIconButton
size="xs"
tuiDropdown
tuiDropdownOpen
tuiDropdownAuto
appearance="flat-grayscale"
iconStart="@tui.ellipsis-vertical"
>
Actions
<tui-data-list *tuiTextfieldDropdown size="s">
<tui-data-list *tuiDropdown size="s">
<button
tuiOption
iconStart="@tui.pencil"
new
(click)="onEdit(subnet)"
>
Rename
@@ -58,7 +51,6 @@ import { SUBNETS_ADD } from './add'
<button
tuiOption
iconStart="@tui.trash"
new
(click)="onDelete($index)"
>
Delete
@@ -74,12 +66,12 @@ import { SUBNETS_ADD } from './add'
</table>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiButton, TuiDropdown, TuiDataList, TuiTextfield],
imports: [TuiButton, TuiDropdown, TuiDataList],
})
export default class Subnets {
private readonly dialogs = inject(TuiDialogService)
private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly api = inject(ApiService)
private readonly loading = inject(LoadingService)
private readonly loading = inject(TuiNotificationMiddleService)
protected readonly subnets = toSignal<MappedSubnet[], []>(
inject<PatchDB<TunnelData>>(PatchDB)
@@ -120,7 +112,7 @@ export default class Subnets {
.pipe(filter(Boolean))
.subscribe(async () => {
const subnet = this.subnets()[index]?.range || ''
const loader = this.loading.open().subscribe()
const loader = this.loading.open('').subscribe()
try {
await this.api.deleteSubnet({ subnet })

View File

@@ -6,7 +6,7 @@ import {
} from '@angular/core'
import { FormsModule } from '@angular/forms'
import { Router } from '@angular/router'
import { TuiButton, TuiError, TuiTextfield } from '@taiga-ui/core'
import { TuiButton, TuiError, TuiInput } from '@taiga-ui/core'
import { TuiButtonLoading } from '@taiga-ui/kit'
import { ApiService } from 'src/app/services/api/api.service'
import { AuthService } from 'src/app/services/auth.service'
@@ -17,7 +17,7 @@ import { AuthService } from 'src/app/services/auth.service'
<form (ngSubmit)="login()">
<tui-textfield [tuiTextfieldCleaner]="false">
<input
tuiTextfield
tuiInput
type="password"
placeholder="Enter password"
[ngModelOptions]="{ standalone: true }"
@@ -59,7 +59,7 @@ import { AuthService } from 'src/app/services/auth.service'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiButton, TuiTextfield, FormsModule, TuiError, TuiButtonLoading],
imports: [TuiButton, TuiInput, FormsModule, TuiError, TuiButtonLoading],
})
export default class Login {
private readonly auth = inject(AuthService)

View File

@@ -1,8 +1,8 @@
import { Component, computed, inject, Injectable, signal } from '@angular/core'
import { toObservable } from '@angular/core/rxjs-interop'
import { ErrorService } from '@start9labs/shared'
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
import { TuiLoader } from '@taiga-ui/core'
import { TuiDialogService } from '@taiga-ui/experimental'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import {
catchError,
@@ -32,7 +32,7 @@ class UpdatingDialog {
export class UpdateService {
private readonly api = inject(ApiService)
private readonly auth = inject(AuthService)
private readonly dialogs = inject(TuiDialogService)
private readonly dialogs = inject(TuiResponsiveDialogService)
private readonly errorService = inject(ErrorService)
readonly result = signal<T.Tunnel.TunnelUpdateResult | null>(null)

View File

@@ -14,7 +14,7 @@
<link rel="icon" type="image/svg+xml" href="/assets/icons/favicon.svg" />
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
</head>
<body tuiTheme="dark">
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -6,6 +6,7 @@
--tui-background-accent-1: #428bf9;
--tui-background-accent-1-hover: #126df7;
--tui-background-accent-1-pressed: #156ed4;
--tui-stroke-width: 1.5px;
}
body {
@@ -41,9 +42,21 @@ tui-dropdown[data-appearance='start-9'] {
backdrop-filter: blur(1rem);
}
tui-dialog[new][data-appearance~='start-9'] {
background: var(--tui-background-neutral-1);
backdrop-filter: blur(5rem);
[tuiTheme='dark'] tui-dialog[data-appearance~='start-9'],
[tuiTheme='dark'] tui-sheet-dialog[data-appearance~='start-9'] .t-sheet {
background:
linear-gradient(45deg, #5240a89c, transparent),
linear-gradient(to bottom, #5240a854, transparent),
color-mix(in hsl, var(--tui-background-elevation-1) 90%, transparent 10%);
background-blend-mode: multiply;
header {
background: transparent;
}
}
tui-notification-middle {
--tui-background-accent-1: var(--tui-status-warning);
}
.g-table {

View File

@@ -14,7 +14,7 @@ import { PatchMonitorService } from './services/patch-monitor.service'
selector: 'app-root',
imports: [TuiRoot, RouterOutlet, ToastContainerComponent],
template: `
<tui-root tuiTheme="dark">
<tui-root>
<router-outlet />
<toast-container />
</tui-root>

View File

@@ -10,7 +10,6 @@ import {
provideZoneChangeDetection,
} from '@angular/core'
import { UntypedFormBuilder } from '@angular/forms'
import { provideAnimations } from '@angular/platform-browser/animations'
import {
ActivationStart,
PreloadAllModules,
@@ -40,21 +39,22 @@ import {
} from '@start9labs/shared'
import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk'
import {
TUI_DATE_FORMAT,
provideTaiga,
TUI_DIALOGS_CLOSE,
TUI_MEDIA,
tuiAlertOptionsProvider,
tuiButtonOptionsProvider,
tuiDateFormatProvider,
tuiDropdownOptionsProvider,
tuiHintOptionsProvider,
tuiNotificationOptionsProvider,
tuiNumberFormatProvider,
} from '@taiga-ui/core'
import { provideEventPlugins } from '@taiga-ui/event-plugins'
import {
TUI_DATE_TIME_VALUE_TRANSFORMER,
TUI_DATE_VALUE_TRANSFORMER,
tuiInputDateOptionsProvider,
tuiInputDateTimeOptionsProvider,
} from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter, identity, merge, of, pairwise } from 'rxjs'
import { filter, identity, merge, pairwise } from 'rxjs'
import { FilterUpdatesPipe } from 'src/app/routes/portal/routes/updates/filter-updates.pipe'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { LiveApiService } from 'src/app/services/api/embassy-live-api.service'
@@ -63,14 +63,16 @@ import { AuthService } from 'src/app/services/auth.service'
import { CategoryService } from 'src/app/services/category.service'
import { ClientStorageService } from 'src/app/services/client-storage.service'
import { ConfigService } from 'src/app/services/config.service'
import { DateTransformerService } from 'src/app/services/date-transformer.service'
import { DatetimeTransformerService } from 'src/app/services/datetime-transformer.service'
import {
PATCH_CACHE,
PatchDbSource,
} from 'src/app/services/patch-db/patch-db-source'
import { StateService } from 'src/app/services/state.service'
import { StorageService } from 'src/app/services/storage.service'
import {
DateTransformer,
DatetimeTransformer,
} from 'src/app/utils/value-transformers'
import { environment } from 'src/environments/environment'
import { ROUTES } from './app.routes'
@@ -83,8 +85,7 @@ const {
export const APP_CONFIG: ApplicationConfig = {
providers: [
provideZoneChangeDetection(),
provideAnimations(),
provideEventPlugins(),
provideTaiga({ mode: 'dark' }),
provideHttpClient(withInterceptorsFromDi(), withFetch()),
provideRouter(
ROUTES,
@@ -107,24 +108,13 @@ export const APP_CONFIG: ApplicationConfig = {
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
tuiButtonOptionsProvider({ size: 'm' }),
tuiDropdownOptionsProvider({ appearance: 'start-os' }),
tuiAlertOptionsProvider({
tuiNotificationOptionsProvider({
autoClose: appearance => (appearance === 'negative' ? 0 : 3000),
}),
{
provide: TUI_DATE_FORMAT,
useValue: of({
mode: 'MDY',
separator: '/',
}),
},
{
provide: TUI_DATE_VALUE_TRANSFORMER,
useClass: DateTransformerService,
},
{
provide: TUI_DATE_TIME_VALUE_TRANSFORMER,
useClass: DatetimeTransformerService,
},
tuiDateFormatProvider({ mode: 'mm/dd/yyyy', separator: '/' }),
tuiInputDateOptionsProvider({ valueTransformer: DateTransformer }),
tuiInputDateTimeOptionsProvider({ valueTransformer: DatetimeTransformer }),
tuiHintOptionsProvider({ appearance: 'primary-grayscale' }),
{
provide: ApiService,
useClass: useMocks ? MockApiService : LiveApiService,

View File

@@ -7,8 +7,7 @@ import {
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { i18nKey, i18nPipe } from '@start9labs/shared'
import { TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { TuiDialogContext, TuiIcon, TuiTitle, TuiCell } from '@taiga-ui/core'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { map } from 'rxjs'

View File

@@ -2,7 +2,7 @@ import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { RouterLink } from '@angular/router'
import { i18nPipe } from '@start9labs/shared'
import { TuiAlert, TuiLink } from '@taiga-ui/core'
import { TuiLink, TuiNotification } from '@taiga-ui/core'
import { PatchDB } from 'patch-db-client'
import { endWith, map, merge, Observable, pairwise, Subject } from 'rxjs'
import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -11,9 +11,9 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
selector: 'notifications-toast',
template: `
<ng-template
[tuiAlert]="!!(visible$ | async)"
[tuiAlertOptions]="{ label: 'StartOS' }"
(tuiAlertChange)="onDismiss()"
[tuiNotification]="!!(visible$ | async)"
[tuiNotificationOptions]="{ label: 'StartOS' }"
(tuiNotificationChange)="onDismiss()"
>
{{ 'New notifications' | i18n }}
<a tuiLink routerLink="/notifications" [queryParams]="{ toast: true }">
@@ -22,7 +22,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiAlert, RouterLink, AsyncPipe, i18nPipe, TuiLink],
imports: [TuiNotification, RouterLink, AsyncPipe, i18nPipe, TuiLink],
})
export class NotificationsToastComponent {
private readonly dismiss$ = new Subject<boolean>()

View File

@@ -2,11 +2,12 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { SwUpdate } from '@angular/service-worker'
import { WA_WINDOW } from '@ng-web-apis/common'
import { i18nPipe, LoadingService } from '@start9labs/shared'
import { i18nPipe } from '@start9labs/shared'
import { Version } from '@start9labs/start-sdk'
import { TuiResponsiveDialog } from '@taiga-ui/addon-mobile'
import { TuiAutoFocus } from '@taiga-ui/cdk'
import { TuiButton } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { distinctUntilChanged, map, merge, Subject } from 'rxjs'
import { ConfigService } from 'src/app/services/config.service'
@@ -74,7 +75,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
export class RefreshAlertComponent {
private readonly win = inject(WA_WINDOW)
private readonly updates = inject(SwUpdate)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly version = Version.parse(inject(ConfigService).version)
readonly i18n = inject(i18nPipe)

View File

@@ -2,13 +2,9 @@ import { CommonModule } from '@angular/common'
import { Component, Inject } from '@angular/core'
import { RouterLink } from '@angular/router'
import { WA_WINDOW } from '@ng-web-apis/common'
import {
DialogService,
i18nKey,
i18nPipe,
LoadingService,
} from '@start9labs/shared'
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
import { TuiButton } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { filter } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service'
@@ -29,7 +25,7 @@ export default class HomePage {
}
constructor(
private readonly loader: LoadingService,
private readonly loader: TuiNotificationMiddleService,
private readonly api: ApiService,
private readonly dialog: DialogService,
@Inject(WA_WINDOW) private readonly window: Window,

View File

@@ -1,7 +1,7 @@
import { Component, inject, DOCUMENT } from '@angular/core'
import { DocsLinkDirective, i18nPipe, RELATIVE_URL } from '@start9labs/shared'
import { TuiButton, TuiIcon, TuiSurface } from '@taiga-ui/core'
import { TuiCardLarge } from '@taiga-ui/layout'
import { TuiButton, TuiIcon } from '@taiga-ui/core'
import { TuiCardLarge, TuiSurface } from '@taiga-ui/layout'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service'

View File

@@ -11,7 +11,7 @@
<label tuiLabel>{{ 'Password' | i18n }}</label>
<input
tuiAutoFocus
tuiTextfield
tuiInput
type="password"
[ngModelOptions]="{ standalone: true }"
[(ngModel)]="password"

View File

@@ -1,4 +1,4 @@
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
.card {
@include taiga.center-all();

View File

@@ -3,10 +3,10 @@ import { Component, DestroyRef, DOCUMENT, inject, Inject } from '@angular/core'
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms'
import { Router } from '@angular/router'
import { i18nKey, i18nPipe, LoadingService } from '@start9labs/shared'
import { i18nKey, i18nPipe } from '@start9labs/shared'
import { TuiAutoFocus } from '@taiga-ui/cdk'
import { TuiButton, TuiError, TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { TuiPassword } from '@taiga-ui/kit'
import { TuiButton, TuiError, TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiNotificationMiddleService, TuiPassword } from '@taiga-ui/kit'
import { TuiCardLarge } from '@taiga-ui/layout'
import { CAWizardComponent } from 'src/app/routes/login/ca-wizard/ca-wizard.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -23,7 +23,7 @@ import { ConfigService } from 'src/app/services/config.service'
CAWizardComponent,
TuiButton,
TuiCardLarge,
TuiTextfield,
TuiInput,
TuiIcon,
TuiPassword,
TuiAutoFocus,
@@ -39,7 +39,7 @@ export default class LoginPage {
constructor(
private readonly router: Router,
private readonly authService: AuthService,
private readonly loader: LoadingService,
private readonly loader: TuiNotificationMiddleService,
private readonly api: ApiService,
public readonly config: ConfigService,
@Inject(DOCUMENT) public readonly document: Document,

View File

@@ -14,7 +14,7 @@ import {
} from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
import { TuiConfirmService } from '@taiga-ui/kit'
import { POLYMORPHEUS_CONTEXT } from '@taiga-ui/polymorpheus'
import { injectContext } from '@taiga-ui/polymorpheus'
import { Operation } from 'fast-json-patch'
import { FormGroupComponent } from 'src/app/routes/portal/components/form/containers/group.component'
import { InvalidService } from 'src/app/routes/portal/components/form/containers/control.directive'
@@ -76,7 +76,7 @@ export interface FormContext<T> {
styles: `
.note {
color: var(--tui-text-secondary);
font: var(--tui-font-text-s);
font: var(--tui-typography-body-s);
margin-top: 1rem;
}
@@ -107,10 +107,9 @@ export class FormComponent<T extends Record<string, any>> implements OnInit {
private readonly confirm = inject(TuiConfirmService, { optional: true })
private readonly formService = inject(FormService)
private readonly invalidService = inject(InvalidService)
private readonly context = inject<TuiDialogContext<void, FormContext<T>>>(
POLYMORPHEUS_CONTEXT,
{ optional: true },
)
private readonly context = injectContext<
TuiDialogContext<void, FormContext<T>>
>({ optional: true })
@Input() spec = this.context?.data.spec || {}
@Input() buttons = this.context?.data.buttons || []

View File

@@ -1,4 +1,3 @@
import { AsyncPipe } from '@angular/common'
import {
ChangeDetectorRef,
Component,
@@ -11,19 +10,14 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
import {
AbstractControl,
FormArrayName,
FormsModule,
ReactiveFormsModule,
} from '@angular/forms'
import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk'
import { TuiAnimated } from '@taiga-ui/cdk'
import {
TuiButton,
TuiError,
TuiIcon,
TuiLink,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiFieldErrorPipe, TuiTooltip } from '@taiga-ui/kit'
import { TuiButton, TuiError, TuiIcon, TuiInput, TuiLink } from '@taiga-ui/core'
import { TuiTooltip } from '@taiga-ui/kit'
import { filter } from 'rxjs'
import { FormService } from 'src/app/services/form.service'
@@ -51,7 +45,13 @@ import { FormObjectComponent } from './object.component'
+ {{ 'Add' | i18n }}
</button>
</div>
<tui-error [error]="order | tuiFieldError | async" />
<!-- TODO: Remove ngModel in Taiga v5.0 -->
<tui-error
[ngModel]=""
[ngModelOptions]="{ standalone: true }"
[formArray]="array.control"
[order]="order"
/>
@for (item of array.control.controls; track item) {
<div tuiAnimated class="control">
<div>
@@ -92,7 +92,7 @@ import { FormObjectComponent } from './object.component'
}
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
:host {
display: block;
@@ -102,7 +102,7 @@ import { FormObjectComponent } from './object.component'
.label {
display: flex;
align-items: center;
font: var(--tui-font-heading-6);
font: var(--tui-typography-heading-h6);
}
.add {
@@ -173,21 +173,20 @@ import { FormObjectComponent } from './object.component'
`,
hostDirectives: [ControlDirective],
imports: [
AsyncPipe,
ReactiveFormsModule,
TuiIcon,
TuiTooltip,
TuiLink,
TuiError,
TuiFieldErrorPipe,
TuiButton,
TuiTextfield,
TuiInput,
i18nPipe,
HintPipe,
MustachePipe,
FormControlComponent,
forwardRef(() => FormObjectComponent),
TuiAnimated,
FormsModule,
],
})
export class FormArrayComponent {

View File

@@ -1,19 +1,15 @@
import { AsyncPipe } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
} from '@angular/core'
import { ReactiveFormsModule } from '@angular/forms'
import { DialogService, i18nPipe } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk'
import { tuiAsControl, TuiControl } from '@taiga-ui/cdk'
import { TuiError } from '@taiga-ui/core'
import {
TUI_FORMAT_ERROR,
TUI_VALIDATION_ERRORS,
TuiFieldErrorPipe,
} from '@taiga-ui/kit'
import { TUI_VALIDATION_ERRORS, TuiError } from '@taiga-ui/core'
import { TUI_FORMAT_ERROR } from '@taiga-ui/kit'
import { PolymorpheusOutlet } from '@taiga-ui/polymorpheus'
import { ControlSpec } from '../controls/control'
@@ -35,7 +31,7 @@ export const ERRORS = [
selector: 'form-control',
template: `
<ng-container *polymorpheusOutlet="controls[spec.type]" />
<tui-error [error]="order | tuiFieldError | async" />
<tui-error [formControl]="$any(control.control)" [order]="order" />
`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
@@ -53,7 +49,7 @@ export const ERRORS = [
},
],
hostDirectives: [ControlDirective],
imports: [AsyncPipe, PolymorpheusOutlet, TuiError, TuiFieldErrorPipe],
imports: [PolymorpheusOutlet, TuiError, ReactiveFormsModule],
})
export class FormControlComponent<
T extends ControlSpec,
@@ -91,7 +87,7 @@ export class FormControlComponent<
.openConfirm({
label: 'Warning',
data: { content: warning as any, yes: 'Confirm', no: 'Cancel' },
closeable: false,
closable: false,
dismissible: false,
})
.subscribe(confirm => {

View File

@@ -3,13 +3,14 @@ import {
ChangeDetectionStrategy,
Component,
Input,
signal,
SkipSelf,
ViewEncapsulation,
} from '@angular/core'
import { ControlContainer, ReactiveFormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk'
import { TUI_DEFAULT_ERROR_MESSAGE } from '@taiga-ui/core'
import { identity, of } from 'rxjs'
import { identity } from 'rxjs'
import { FilterHiddenPipe } from '../pipes/filter-hidden.pipe'
import { FormArrayComponent } from './array.component'
@@ -92,7 +93,7 @@ import { FormUnionComponent } from './union.component'
viewProviders: [
{
provide: TUI_DEFAULT_ERROR_MESSAGE,
useValue: of('Unknown error'),
useValue: signal('Unknown error'),
},
{
provide: ControlContainer,

View File

@@ -9,8 +9,7 @@ import {
} from '@angular/core'
import { ControlContainer } from '@angular/forms'
import { IST } from '@start9labs/start-sdk'
import { TuiButton, TuiIcon } from '@taiga-ui/core'
import { TuiExpand } from '@taiga-ui/experimental'
import { TuiButton, TuiIcon, TuiExpand } from '@taiga-ui/core'
import { TuiTooltip } from '@taiga-ui/kit'
import { ControlDirective } from './control.directive'
@@ -43,7 +42,7 @@ import { FormGroupComponent } from './group.component'
</tui-expand>
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
:host {
display: flex;
@@ -57,7 +56,7 @@ import { FormGroupComponent } from './group.component'
display: flex;
align-items: center;
cursor: pointer;
font: var(--tui-font-text-l);
font: var(--tui-typography-body-l);
font-weight: bold;
margin: 0 0 -0.75rem;
}

View File

@@ -1,3 +1,4 @@
import { TuiElasticContainer } from '@taiga-ui/layout'
import {
ChangeDetectionStrategy,
Component,
@@ -13,7 +14,6 @@ import {
} from '@angular/forms'
import { IST } from '@start9labs/start-sdk'
import { TuiValueChanges } from '@taiga-ui/cdk'
import { TuiElasticContainer } from '@taiga-ui/kit'
import { FormService } from 'src/app/services/form.service'
import { FormControlComponent } from './control.component'
import { FormGroupComponent } from './group.component'

View File

@@ -1,7 +1,7 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk'
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiInputColor, TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control'
@@ -35,7 +35,7 @@ import { HintPipe } from '../pipes/hint.pipe'
`,
imports: [
FormsModule,
TuiTextfield,
TuiInput,
TuiInputColor,
TuiIcon,
TuiTooltip,

View File

@@ -8,7 +8,7 @@ import {
TuiMapperPipe,
TuiTime,
} from '@taiga-ui/cdk'
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { TuiIcon, TuiInput } from '@taiga-ui/core'
import {
TuiInputDate,
TuiInputDateTime,
@@ -22,21 +22,23 @@ import { HintPipe } from '../pipes/hint.pipe'
@Component({
selector: 'form-datetime',
template: `
<!--
TODO: Move @switch down to only affect <input ... /> after fix:
https://github.com/taiga-family/taiga-ui/issues/11780
-->
@switch (spec.inputmode) {
@case ('time') {
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
@if (spec.name) {
<label tuiLabel>
{{ spec.name }}
@if (spec.required) {
<span>*</span>
}
</label>
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
@if (spec.name) {
<label tuiLabel>
{{ spec.name }}
@if (spec.required) {
<span>*</span>
}
</label>
}
@if (spec | hint; as hint) {
<tui-icon [tuiTooltip]="hint" />
}
@if (spec.inputmode !== 'time') {
<tui-calendar *tuiDropdown />
}
@switch (spec.inputmode) {
@case ('time') {
<input
tuiInputTime
type="time"
@@ -47,21 +49,8 @@ import { HintPipe } from '../pipes/hint.pipe'
(ngModelChange)="value = $event?.toString() || null"
(blur)="control.onTouched()"
/>
@if (spec | hint; as hint) {
<tui-icon [tuiTooltip]="hint" />
}
</tui-textfield>
}
@case ('date') {
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
@if (spec.name) {
<label tuiLabel>
{{ spec.name }}
@if (spec.required) {
<span>*</span>
}
</label>
}
}
@case ('date') {
<input
tuiInputDate
type="date"
@@ -73,22 +62,8 @@ import { HintPipe } from '../pipes/hint.pipe'
[(ngModel)]="value"
(blur)="control.onTouched()"
/>
@if (spec | hint; as hint) {
<tui-icon [tuiTooltip]="hint" />
}
<tui-calendar *tuiTextfieldDropdown />
</tui-textfield>
}
@case ('datetime-local') {
<tui-textfield (tuiActiveZoneChange)="!$event && control.onTouched()">
@if (spec.name) {
<label tuiLabel>
{{ spec.name }}
@if (spec.required) {
<span>*</span>
}
</label>
}
}
@case ('datetime-local') {
<input
tuiInputDateTime
type="datetime-local"
@@ -100,17 +75,13 @@ import { HintPipe } from '../pipes/hint.pipe'
[(ngModel)]="value"
(blur)="control.onTouched()"
/>
@if (spec | hint; as hint) {
<tui-icon [tuiTooltip]="hint" />
}
<tui-calendar *tuiTextfieldDropdown />
</tui-textfield>
}
}
}
</tui-textfield>
`,
imports: [
FormsModule,
TuiTextfield,
TuiInput,
TuiIcon,
TuiTooltip,
TuiInputTime,

View File

@@ -30,7 +30,7 @@ import { Control } from './control'
}
</div>
@if (value) {
<tui-chip>
<span tuiChip>
{{ value.name }}
<button
tuiIconButton
@@ -42,7 +42,7 @@ import { Control } from './control'
>
{{ 'Delete' | i18n }}
</button>
</tui-chip>
</span>
} @else {
<small>{{ 'Click or drop file here' | i18n }}</small>
}
@@ -54,7 +54,7 @@ import { Control } from './control'
</label>
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
.template {
@include taiga.transition(opacity);
@@ -63,7 +63,7 @@ import { Control } from './control'
display: flex;
align-items: center;
padding: 0 0.5rem;
font: var(--tui-font-text-m);
font: var(--tui-typography-body-m);
font-weight: bold;
&_hidden {

View File

@@ -29,12 +29,6 @@ import { Control } from './control'
}
</tui-textfield>
`,
styles: `
// TODO: Remove after Taiga UI update
:host ::ng-deep .t-input {
pointer-events: none;
}
`,
imports: [
FormsModule,
TuiTextfield,

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk'
import { TuiIcon, TuiNumberFormat, TuiTextfield } from '@taiga-ui/core'
import { TuiIcon, TuiNumberFormat, TuiInput } from '@taiga-ui/core'
import { TuiInputNumber, TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control'
@@ -39,7 +39,7 @@ import { HintPipe } from '../pipes/hint.pipe'
`,
imports: [
FormsModule,
TuiTextfield,
TuiInput,
TuiInputNumber,
TuiNumberFormat,
TuiIcon,

View File

@@ -1,10 +1,10 @@
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import { Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { Router, RouterLink } from '@angular/router'
import { invert } from '@start9labs/shared'
import { IST } from '@start9labs/start-sdk'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import { TuiDataList, TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { TuiDataList, TuiIcon, TuiInput } from '@taiga-ui/core'
import {
TuiChevron,
TuiFluidTypography,
@@ -50,12 +50,11 @@ import { HintPipe } from '../pipes/hint.pipe'
/>
}
@if (!mobile) {
<tui-data-list *tuiTextfieldDropdown>
<tui-data-list *tuiDropdown>
@for (item of items; track item) {
@if (inverted[item]?.startsWith('~')) {
<a
tuiOption
new
iconEnd="@tui.arrow-right"
tuiFluidTypography
[routerLink]="inverted[item]?.slice(1)"
@@ -65,7 +64,6 @@ import { HintPipe } from '../pipes/hint.pipe'
} @else {
<button
tuiOption
new
tuiFluidTypography
[style.white-space]="'nowrap'"
[value]="item"
@@ -85,7 +83,7 @@ import { HintPipe } from '../pipes/hint.pipe'
imports: [
FormsModule,
RouterLink,
TuiTextfield,
TuiInput,
TuiSelect,
TuiDataList,
TuiFluidTypography,
@@ -98,7 +96,7 @@ import { HintPipe } from '../pipes/hint.pipe'
export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> {
protected readonly router = inject(Router)
protected readonly inverted = invert(this.spec.values)
protected readonly mobile = inject(TUI_IS_MOBILE)
protected readonly mobile = inject(WA_IS_MOBILE)
protected readonly items = Object.values(this.spec.values)
protected readonly disabledItemHandler = (item: string) =>
Array.isArray(this.spec.disabled) &&

View File

@@ -3,7 +3,7 @@ import { FormsModule } from '@angular/forms'
import { i18nPipe } from '@start9labs/shared'
import { IST, utils } from '@start9labs/start-sdk'
import { tuiInjectElement } from '@taiga-ui/cdk'
import { TuiButton, TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { TuiButton, TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control'
@@ -22,7 +22,7 @@ import { HintPipe } from '../pipes/hint.pipe'
</label>
}
<input
tuiTextfield
tuiInput
[attr.inputmode]="spec.inputmode"
[attr.minLength]="spec.minLength"
[attr.maxLength]="spec.maxLength"
@@ -86,7 +86,7 @@ import { HintPipe } from '../pipes/hint.pipe'
`,
imports: [
FormsModule,
TuiTextfield,
TuiInput,
TuiButton,
TuiIcon,
TuiTooltip,

View File

@@ -1,7 +1,7 @@
import { Component } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IST } from '@start9labs/start-sdk'
import { TuiIcon, TuiTextfield } from '@taiga-ui/core'
import { TuiIcon, TuiInput } from '@taiga-ui/core'
import { TuiTextarea, TuiTooltip } from '@taiga-ui/kit'
import { Control } from './control'
@@ -37,14 +37,7 @@ import { HintPipe } from '../pipes/hint.pipe'
}
</tui-textfield>
`,
imports: [
FormsModule,
TuiTextfield,
TuiTextarea,
TuiIcon,
TuiTooltip,
HintPipe,
],
imports: [FormsModule, TuiInput, TuiTextarea, TuiIcon, TuiTooltip, HintPipe],
})
export class FormTextareaComponent extends Control<
IST.ValueSpecTextarea,

View File

@@ -2,9 +2,8 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { CopyService, i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiButton, TuiHint, TuiTitle } from '@taiga-ui/core'
import { TuiButton, TuiHint, TuiTitle, TuiCell } from '@taiga-ui/core'
import { TuiFade } from '@taiga-ui/kit'
import { TuiCell } from '@taiga-ui/layout'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { ConfigService } from 'src/app/services/config.service'

View File

@@ -22,7 +22,7 @@ import { HeaderStatusComponent } from './status.component'
<header-menu class="item item_corner" />
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
@keyframes connecting {
25%,
@@ -122,7 +122,7 @@ import { HeaderStatusComponent } from './status.component'
display: flex;
height: 100%;
align-items: center;
font: var(--tui-font-text-l);
font: var(--tui-typography-body-l);
padding: 1rem;
white-space: nowrap;
overflow: hidden;

View File

@@ -5,7 +5,6 @@ import {
DocsLinkDirective,
ErrorService,
i18nPipe,
LoadingService,
SafeLinksDirective,
} from '@start9labs/shared'
import {
@@ -15,6 +14,7 @@ import {
TuiHint,
TuiIcon,
} from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { filter } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { AuthService } from 'src/app/services/auth.service'
@@ -43,71 +43,60 @@ import { ABOUT } from './about.component'
{{ status().message | i18n }}
</div>
}
<tui-data-list [style.width.rem]="13">
<tui-opt-group>
<button tuiOption iconStart="@tui.info" new (click)="about()">
{{ 'About this server' | i18n }}
</button>
</tui-opt-group>
<tui-opt-group label="" safeLinks>
<a
tuiOption
docsLink
new
iconStart="@tui.book-open-text"
path="/start-os/index.html"
>
{{ 'User manual' | i18n }}
</a>
<a
tuiOption
new
iconStart="@tui.headphones"
href="https://start9.com/contact"
>
{{ 'Contact support' | i18n }}
</a>
<a
tuiOption
new
iconStart="@tui.dollar-sign"
href="https://donate.start9.com"
>
{{ 'Donate to Start9' | i18n }}
</a>
</tui-opt-group>
<tui-opt-group label="">
<a
tuiOption
new
iconStart="@tui.settings"
routerLink="/system"
(click)="open = false"
>
{{ 'System Settings' | i18n }}
</a>
</tui-opt-group>
<tui-opt-group label="">
<button
tuiOption
new
iconStart="@tui.refresh-cw"
(click)="promptPower('restart')"
>
{{ 'Restart' | i18n }}
</button>
<button
tuiOption
new
iconStart="@tui.power"
(click)="promptPower('shutdown')"
>
{{ 'Shutdown' | i18n }}
</button>
<button tuiOption new iconStart="@tui.log-out" (click)="logout()">
{{ 'Logout' | i18n }}
</button>
</tui-opt-group>
<tui-data-list safeLinks [style.width.rem]="13">
<button tuiOption iconStart="@tui.info" (click)="about()">
{{ 'About this server' | i18n }}
</button>
<hr />
<a
tuiOption
docsLink
iconStart="@tui.book-open-text"
path="/start-os/index.html"
>
{{ 'User manual' | i18n }}
</a>
<a
tuiOption
iconStart="@tui.headphones"
href="https://start9.com/contact"
>
{{ 'Contact support' | i18n }}
</a>
<a
tuiOption
iconStart="@tui.dollar-sign"
href="https://donate.start9.com"
>
{{ 'Donate to Start9' | i18n }}
</a>
<hr />
<a
tuiOption
iconStart="@tui.settings"
routerLink="/system"
(click)="open = false"
>
{{ 'System Settings' | i18n }}
</a>
<hr />
<button
tuiOption
iconStart="@tui.refresh-cw"
(click)="promptPower('restart')"
>
{{ 'Restart' | i18n }}
</button>
<button
tuiOption
iconStart="@tui.power"
(click)="promptPower('shutdown')"
>
{{ 'Shutdown' | i18n }}
</button>
<button tuiOption iconStart="@tui.log-out" (click)="logout()">
{{ 'Logout' | i18n }}
</button>
</tui-data-list>
</ng-template>
`,
@@ -151,7 +140,7 @@ import { ABOUT } from './about.component'
export class HeaderMenuComponent {
private readonly api = inject(ApiService)
private readonly auth = inject(AuthService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
private readonly dialog = inject(DialogService)

View File

@@ -32,7 +32,7 @@ import { getMenu } from 'src/app/utils/system-utilities'
}
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
:host {
position: relative;

View File

@@ -18,7 +18,7 @@ import { STATUS } from 'src/app/services/status.service'
<span>{{ status().message | i18n }}</span>
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
:host {
@include taiga.transition(all);

View File

@@ -5,21 +5,21 @@ import {
input,
signal,
} from '@angular/core'
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import {
CopyService,
DialogService,
ErrorService,
i18nPipe,
LoadingService,
} from '@start9labs/shared'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import {
TuiButton,
tuiButtonOptionsProvider,
TuiDataList,
TuiDropdown,
TuiTextfield,
TuiInput,
} from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { QRModal } from 'src/app/routes/portal/modals/qr.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -114,11 +114,10 @@ import { DomainHealthService } from './domain-health.service'
[(tuiDropdownOpen)]="open"
>
{{ 'Actions' | i18n }}
<tui-data-list *tuiTextfieldDropdown (click)="open.set(false)">
<tui-data-list *tuiDropdown (click)="open.set(false)">
@if (address().ui) {
<a
tuiOption
new
iconStart="@tui.external-link"
target="_blank"
rel="noreferrer"
@@ -130,7 +129,6 @@ import { DomainHealthService } from './domain-health.service'
}
<button
tuiOption
new
[iconStart]="
address().enabled ? '@tui.toggle-right' : '@tui.toggle-left'
"
@@ -138,12 +136,11 @@ import { DomainHealthService } from './domain-health.service'
>
{{ (address().enabled ? 'Disable' : 'Enable') | i18n }}
</button>
<button tuiOption new iconStart="@tui.qr-code" (click)="showQR()">
<button tuiOption iconStart="@tui.qr-code" (click)="showQR()">
{{ 'Show QR' | i18n }}
</button>
<button
tuiOption
new
iconStart="@tui.copy"
(click)="copyService.copy(address().url)"
>
@@ -152,7 +149,6 @@ import { DomainHealthService } from './domain-health.service'
@if (address().hostnameInfo.metadata.kind === 'public-domain') {
<button
tuiOption
new
iconStart="@tui.settings"
(click)="showDnsValidation()"
>
@@ -162,7 +158,6 @@ import { DomainHealthService } from './domain-health.service'
@if (address().hostnameInfo.metadata.kind === 'private-domain') {
<button
tuiOption
new
iconStart="@tui.settings"
(click)="showPrivateDnsValidation()"
>
@@ -175,7 +170,6 @@ import { DomainHealthService } from './domain-health.service'
) {
<button
tuiOption
new
iconStart="@tui.settings"
(click)="showPortForwardValidation()"
>
@@ -183,12 +177,7 @@ import { DomainHealthService } from './domain-health.service'
</button>
}
@if (address().deletable) {
<button
tuiOption
new
iconStart="@tui.trash"
(click)="deleteDomain()"
>
<button tuiOption iconStart="@tui.trash" (click)="deleteDomain()">
{{ 'Delete' | i18n }}
</button>
}
@@ -224,15 +213,15 @@ import { DomainHealthService } from './domain-health.service'
}
}
`,
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiTextfield],
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiInput],
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AddressActionsComponent {
private readonly isMobile = inject(TUI_IS_MOBILE)
private readonly isMobile = inject(WA_IS_MOBILE)
private readonly dialog = inject(DialogService)
private readonly api = inject(ApiService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
private readonly domainHealth = inject(DomainHealthService)
readonly copyService = inject(CopyService)
@@ -247,8 +236,8 @@ export class AddressActionsComponent {
showQR() {
this.dialog
.openComponent(new PolymorpheusComponent(QRModal), {
size: 'auto',
closeable: this.isMobile,
size: 's',
closable: this.isMobile,
data: this.address().url,
})
.subscribe()

View File

@@ -5,14 +5,10 @@ import {
input,
signal,
} from '@angular/core'
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared'
import { ErrorService, i18nPipe } from '@start9labs/shared'
import { ISB, utils } from '@start9labs/start-sdk'
import {
TuiButton,
TuiDataList,
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiButton, TuiDataList, TuiDropdown, TuiInput } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { firstValueFrom } from 'rxjs'
import {
@@ -46,11 +42,11 @@ import { InterfaceAddressItemComponent } from './item.component'
[(tuiDropdownOpen)]="addOpen"
>
{{ 'Add Domain' | i18n }}
<tui-data-list *tuiTextfieldDropdown (click)="addOpen.set(false)">
<button tuiOption new (click)="addPublicDomain()">
<tui-data-list *tuiDropdown (click)="addOpen.set(false)">
<button tuiOption (click)="addPublicDomain()">
{{ 'Public Domain' | i18n }}
</button>
<button tuiOption new (click)="addPrivateDomain()">
<button tuiOption (click)="addPrivateDomain()">
{{ 'Private Domain' | i18n }}
</button>
</tui-data-list>
@@ -97,7 +93,7 @@ import { InterfaceAddressItemComponent } from './item.component'
TuiButton,
TuiDropdown,
TuiDataList,
TuiTextfield,
TuiInput,
TableComponent,
PlaceholderComponent,
i18nPipe,
@@ -108,7 +104,7 @@ import { InterfaceAddressItemComponent } from './item.component'
export class InterfaceAddressesComponent {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly formDialog = inject(FormDialogService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService)
private readonly i18n = inject(i18nPipe)

View File

@@ -189,7 +189,7 @@ export type DomainValidationData = {
td,
th {
padding: 0.5rem 0.5rem !important;
font: var(--tui-font-text-s) !important;
font: var(--tui-typography-body-s) !important;
color: var(--tui-text-primary) !important;
font-weight: normal !important;
}

View File

@@ -6,11 +6,15 @@ import {
input,
signal,
} from '@angular/core'
import { ErrorService, i18nPipe, LoadingService } from '@start9labs/shared'
import { FormsModule } from '@angular/forms'
import { ErrorService, i18nPipe } from '@start9labs/shared'
import { TuiObfuscatePipe } from '@taiga-ui/cdk'
import { TuiButton, TuiIcon } from '@taiga-ui/core'
import { FormsModule } from '@angular/forms'
import { TuiBadge, TuiSwitch } from '@taiga-ui/kit'
import {
TuiBadge,
TuiNotificationMiddleService,
TuiSwitch,
} from '@taiga-ui/kit'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { GatewayAddress, MappedServiceInterface } from '../interface.service'
import { AddressActionsComponent } from './actions.component'
@@ -45,12 +49,13 @@ import { DomainHealthService } from './domain-health.service'
</span>
</td>
<td class="type">
<tui-badge
<span
tuiBadge
size="s"
[appearance]="typeAppearance(address.hostnameInfo.metadata.kind)"
>
{{ address.type }}
</tui-badge>
</span>
</td>
<td>
<div class="cert">
@@ -177,7 +182,7 @@ import { DomainHealthService } from './domain-health.service'
.access {
padding-right: 0;
font: var(--tui-font-text-m);
font: var(--tui-typography-body-m);
font-weight: bold;
tui-icon {
@@ -186,7 +191,7 @@ import { DomainHealthService } from './domain-health.service'
}
.type {
font: var(--tui-font-text-m);
font: var(--tui-typography-body-m);
font-weight: bold;
color: var(--tui-text-primary);
padding-inline-end: 0.5rem;
@@ -226,7 +231,7 @@ import { DomainHealthService } from './domain-health.service'
export class InterfaceAddressItemComponent {
private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly domainHealth = inject(DomainHealthService)
readonly address = input.required<GatewayAddress>()

View File

@@ -1,3 +1,4 @@
import { WA_IS_MOBILE } from '@ng-web-apis/platform'
import {
ChangeDetectionStrategy,
Component,
@@ -7,13 +8,7 @@ import {
} from '@angular/core'
import { CopyService, DialogService, i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import {
TuiButton,
TuiDataList,
TuiDropdown,
TuiTextfield,
} from '@taiga-ui/core'
import { TuiButton, TuiDataList, TuiDropdown, TuiInput } from '@taiga-ui/core'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
import { TableComponent } from 'src/app/routes/portal/components/table.component'
@@ -109,7 +104,7 @@ import {
>
{{ 'More' | i18n }}
<tui-data-list
*tuiTextfieldDropdown
*tuiDropdown
(click)="overflowOpen.set(null)"
>
@for (
@@ -120,7 +115,6 @@ import {
@if (pluginGroup().pluginActions[actionId]; as meta) {
<button
tuiOption
new
(click)="runRowAction(actionId, meta, address)"
>
{{ meta.name }}
@@ -142,7 +136,7 @@ import {
[(tuiDropdownOpen)]="open"
>
{{ 'Actions' | i18n }}
<tui-data-list *tuiTextfieldDropdown (click)="open.set(false)">
<tui-data-list *tuiDropdown (click)="open.set(false)">
@if (address.hostnameInfo.metadata.kind === 'plugin') {
@if (address.hostnameInfo.metadata.removeAction) {
@if (
@@ -153,7 +147,6 @@ import {
) {
<button
tuiOption
new
iconStart="@tui.trash"
(click)="
runRowAction(
@@ -170,7 +163,6 @@ import {
}
<button
tuiOption
new
iconStart="@tui.qr-code"
(click)="showQR(address.url)"
>
@@ -178,7 +170,6 @@ import {
</button>
<button
tuiOption
new
iconStart="@tui.copy"
(click)="copyService.copy(address.url)"
>
@@ -192,7 +183,6 @@ import {
@if (pluginGroup().pluginActions[actionId]; as meta) {
<button
tuiOption
new
(click)="runRowAction(actionId, meta, address)"
>
{{ meta.name }}
@@ -271,7 +261,7 @@ import {
TuiButton,
TuiDropdown,
TuiDataList,
TuiTextfield,
TuiInput,
TableComponent,
PlaceholderComponent,
i18nPipe,
@@ -279,7 +269,7 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PluginAddressesComponent {
private readonly isMobile = inject(TUI_IS_MOBILE)
private readonly isMobile = inject(WA_IS_MOBILE)
private readonly dialog = inject(DialogService)
private readonly actionService = inject(ActionService)
readonly copyService = inject(CopyService)
@@ -290,12 +280,12 @@ export class PluginAddressesComponent {
readonly packageId = input('')
readonly value = input<MappedServiceInterface | undefined>()
showQR(url: string) {
showQR(data: string) {
this.dialog
.openComponent(new PolymorpheusComponent(QRModal), {
size: 'auto',
closeable: this.isMobile,
data: url,
size: 's',
closable: this.isMobile,
data,
})
.subscribe()
}

View File

@@ -116,7 +116,7 @@ export type PortForwardValidationData = {
td,
th {
padding: 0.5rem 0.5rem !important;
font: var(--tui-font-text-s) !important;
font: var(--tui-typography-body-s) !important;
color: var(--tui-text-primary) !important;
font-weight: normal !important;
}

View File

@@ -119,7 +119,7 @@ export type PrivateDnsValidationData = {
td,
th {
padding: 0.5rem 0.5rem !important;
font: var(--tui-font-text-s) !important;
font: var(--tui-typography-body-s) !important;
color: var(--tui-text-primary) !important;
font-weight: normal !important;
}

View File

@@ -29,7 +29,7 @@ import { PluginAddressesComponent } from './addresses/plugin.component'
flex-direction: column;
gap: 1rem;
color: var(--tui-text-secondary);
font: var(--tui-font-text-l);
font: var(--tui-typography-body-l);
::ng-deep [tuiSkeleton] {
width: 100%;

View File

@@ -3,9 +3,9 @@ import {
convertAnsi,
DownloadHTMLService,
ErrorService,
LoadingService,
} from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { LogsComponent } from './logs.component'
@Directive({
@@ -13,7 +13,7 @@ import { LogsComponent } from './logs.component'
})
export class LogsDownloadDirective {
private readonly component = inject(LogsComponent)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
private readonly downloadHtml = inject(DownloadHTMLService)

View File

@@ -1,5 +1,5 @@
import { Directive, inject, Output } from '@angular/core'
import { IntersectionObserveeService } from '@ng-web-apis/intersection-observer'
import { WaIntersectionObserveeService } from '@ng-web-apis/intersection-observer'
import { convertAnsi, ErrorService } from '@start9labs/shared'
import { catchError, defer, filter, from, map, of, switchMap, tap } from 'rxjs'
import { LogsComponent } from './logs.component'
@@ -8,7 +8,7 @@ import { LogsComponent } from './logs.component'
selector: '[logsFetch]',
})
export class LogsFetchDirective {
private readonly observer = inject(IntersectionObserveeService)
private readonly observer = inject(WaIntersectionObserveeService)
private readonly component = inject(LogsComponent)
private readonly errors = inject(ErrorService)

View File

@@ -19,7 +19,7 @@ import { TuiIcon } from '@taiga-ui/core'
justify-content: center;
text-align: center;
padding: 1rem;
font: var(--tui-font-text-l);
font: var(--tui-typography-body-l);
color: var(--tui-text-tertiary);
tui-icon {

View File

@@ -75,7 +75,7 @@ const FILTER = ['services', 'system', 'marketplace']
</nav>
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
:host {
display: none;

View File

@@ -14,14 +14,14 @@ import { tuiIsNumber } from '@taiga-ui/cdk'
selector: 'task-info',
template: `
@if (diff.length) {
<tui-notification>
<div tuiNotification>
{{ 'The following modifications were made' | i18n }}:
<ul>
@for (d of diff; track d) {
<li [innerHTML]="d"></li>
}
</ul>
</tui-notification>
</div>
}
`,
imports: [TuiNotification, i18nPipe],

View File

@@ -6,9 +6,19 @@ import {
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { RouterOutlet } from '@angular/router'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiButton, TuiIcon, TuiLoader, TuiScrollbar } from '@taiga-ui/core'
import { TuiActionBar, TuiProgress } from '@taiga-ui/kit'
import { ErrorService } from '@start9labs/shared'
import {
TuiButton,
TuiIcon,
TuiLoader,
TuiPopup,
TuiScrollbar,
} from '@taiga-ui/core'
import {
TuiActionBar,
TuiNotificationMiddleService,
TuiProgress,
} from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { TabsComponent } from 'src/app/routes/portal/components/tabs.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -26,7 +36,7 @@ import { HeaderComponent } from './components/header/header.component'
</main>
<app-tabs />
@if (update(); as update) {
<tui-action-bar *tuiActionBar="bar()">
<tui-action-bar *tuiPopup="bar()">
@if (update === true) {
<tui-icon icon="@tui.check" class="g-positive" />
Download complete, restart to apply changes
@@ -52,7 +62,7 @@ import { HeaderComponent } from './components/header/header.component'
}
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
:host {
height: 100%;
@@ -93,10 +103,11 @@ import { HeaderComponent } from './components/header/header.component'
TuiLoader,
TuiIcon,
TuiButton,
TuiPopup,
],
})
export class PortalComponent {
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
private readonly api = inject(ApiService)

View File

@@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { TuiDialogService, TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { TuiDialogService, TuiIcon, TuiTitle, TuiCell } from '@taiga-ui/core'
import { BackupsUpcomingComponent } from './components/upcoming.component'
import { HISTORY } from './modals/history.component'
import { JOBS } from './modals/jobs.component'

View File

@@ -2,16 +2,14 @@ import { Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import {
TuiButton,
TuiCheckbox,
TuiDialogContext,
TuiDialogOptions,
TuiGroup,
TuiLoader,
} from '@taiga-ui/core'
import { TuiBlock, TuiCheckbox } from '@taiga-ui/kit'
import {
POLYMORPHEUS_CONTEXT,
PolymorpheusComponent,
} from '@taiga-ui/polymorpheus'
import { TuiBlock } from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { firstValueFrom, map } from 'rxjs'
import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -74,9 +72,7 @@ interface Package {
export class BackupsBackupModal {
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
readonly context =
inject<TuiDialogContext<string[], { btnText: string } | undefined>>(
POLYMORPHEUS_CONTEXT,
)
injectContext<TuiDialogContext<string[], { btnText: string } | undefined>>()
hasSelection = false

View File

@@ -1,17 +1,21 @@
import { Component, inject } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { ErrorService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import {
TuiButton,
TuiDialogContext,
TuiDialogService,
TuiTextfield,
TuiInput,
} from '@taiga-ui/core'
import { TuiBadge, TuiSwitch } from '@taiga-ui/kit'
import {
TuiBadge,
TuiNotificationMiddleService,
TuiSwitch,
} from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { from, map } from 'rxjs'
import { T } from '@start9labs/start-sdk'
import { BackupJob } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe'
@@ -25,7 +29,7 @@ import { TARGET, TARGET_CREATE } from './target.component'
<tui-textfield>
<label tuiLabel>Job Name</label>
<input
tuiTextfield
tuiInput
name="name"
[(ngModel)]="job.name"
placeholder="My Backup Job"
@@ -40,11 +44,12 @@ import { TARGET, TARGET_CREATE } from './target.component'
(click)="selectTarget()"
>
Target
<tui-badge
<span
tuiBadge
[appearance]="target()?.[job.targetId]?.type ? 'success' : 'warning'"
>
{{ target()?.[job.targetId]?.type || 'Select target' }}
</tui-badge>
</span>
</button>
<button
tuiButton
@@ -55,14 +60,17 @@ import { TARGET, TARGET_CREATE } from './target.component'
(click)="selectPackages()"
>
Packages
<tui-badge [appearance]="job.packageIds.length ? 'success' : 'warning'">
<span
tuiBadge
[appearance]="job.packageIds.length ? 'success' : 'warning'"
>
{{ job.packageIds.length + ' selected' }}
</tui-badge>
</span>
</button>
<tui-textfield>
<label tuiLabel>Schedule</label>
<input
tuiTextfield
tuiInput
name="cron"
[(ngModel)]="job.cron"
placeholder="* * * * *"
@@ -109,7 +117,7 @@ import { TARGET, TARGET_CREATE } from './target.component'
`,
imports: [
FormsModule,
TuiTextfield,
TuiInput,
TuiSwitch,
TuiButton,
TuiBadge,
@@ -119,7 +127,7 @@ import { TARGET, TARGET_CREATE } from './target.component'
export class BackupsEditModal {
private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly dialogs = inject(TuiDialogService)
private readonly context =
injectContext<TuiDialogContext<BackupJob, BackupJobBuilder>>()

View File

@@ -1,5 +1,3 @@
import { toSignal } from '@angular/core/rxjs-interop'
import { TuiCheckbox, TuiSkeleton } from '@taiga-ui/kit'
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
@@ -7,10 +5,18 @@ import {
inject,
signal,
} from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { TUI_TRUE_HANDLER, TUI_FALSE_HANDLER } from '@taiga-ui/cdk'
import { TuiDialogService, TuiIcon, TuiLink, TuiButton } from '@taiga-ui/core'
import { ErrorService } from '@start9labs/shared'
import { TUI_FALSE_HANDLER, TUI_TRUE_HANDLER } from '@taiga-ui/cdk'
import {
TuiButton,
TuiCheckbox,
TuiDialogService,
TuiIcon,
TuiLink,
} from '@taiga-ui/core'
import { TuiNotificationMiddleService, TuiSkeleton } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { from } from 'rxjs'
import { REPORT } from 'src/app/components/backup-report.component'
@@ -101,7 +107,7 @@ import { HasErrorPipe } from '../pipes/has-error.pipe'
</table>
`,
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
@use '@taiga-ui/styles/utils' as taiga;
tui-icon {
font-size: 1rem;
@@ -170,7 +176,7 @@ export class BackupsHistoryModal {
private readonly api = inject(ApiService)
private readonly dialogs = inject(TuiDialogService)
private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
readonly targets = toSignal(from(this.api.getBackupTargets({})))
readonly runs = signal<BackupRun[] | null>(null)

View File

@@ -1,16 +1,22 @@
import { Component, inject, OnInit } from '@angular/core'
import { toSignal } from '@angular/core/rxjs-interop'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { ErrorService } from '@start9labs/shared'
import {
TuiButton,
TuiDialogOptions,
TuiDialogService,
TuiIcon,
TuiButton,
TuiNotification,
TuiLink,
TuiNotification,
} from '@taiga-ui/core'
import { TuiConfirmData, TUI_CONFIRM, TuiSkeleton } from '@taiga-ui/kit'
import {
TUI_CONFIRM,
TuiConfirmData,
TuiNotificationMiddleService,
TuiSkeleton,
} from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
import { BehaviorSubject, filter, from } from 'rxjs'
import { BackupJob } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@@ -18,18 +24,17 @@ import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe'
import { ToHumanCronPipe } from '../pipes/to-human-cron.pipe'
import { BackupJobBuilder } from '../utils/job-builder'
import { EDIT } from './edit.component'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({
template: `
<tui-notification>
<div tuiNotification>
Scheduling automatic backups is an excellent way to ensure your StartOS
data is safely backed up. StartOS will issue a notification whenever one
of your scheduled backups succeeds or fails.
<a tuiLink docsLink path="/start-os/backup-create.html">
View instructions
</a>
</tui-notification>
</div>
<h3 class="g-title">
Saved Jobs
<button tuiButton size="s" iconStart="@tui.plus" (click)="create()">
@@ -147,7 +152,7 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api'
})
export class BackupsJobsModal implements OnInit {
private readonly dialogs = inject(TuiDialogService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
private readonly api = inject(ApiService)

View File

@@ -1,14 +1,19 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { ErrorService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiMapperPipe } from '@taiga-ui/cdk'
import { TuiButton, TuiDialogContext, TuiGroup } from '@taiga-ui/core'
import { TuiBlock, TuiCheckbox } from '@taiga-ui/kit'
import {
TuiButton,
TuiCheckbox,
TuiDialogContext,
TuiGroup,
} from '@taiga-ui/core'
import { TuiBlock, TuiNotificationMiddleService } from '@taiga-ui/kit'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { take } from 'rxjs'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { ToOptionsPipe } from '../pipes/to-options.pipe'
@@ -68,7 +73,7 @@ import { RecoverOption } from '../types/recover-option'
})
export class BackupsRecoverModal {
private readonly api = inject(ApiService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly errorService = inject(ErrorService)
private readonly context =
injectContext<TuiDialogContext<void, RecoverData>>()

View File

@@ -15,8 +15,8 @@ import {
TuiIcon,
TuiLoader,
TuiTitle,
TuiCell,
} from '@taiga-ui/core'
import { TuiCell } from '@taiga-ui/layout'
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { PatchDB } from 'patch-db-client'
import { ApiService } from 'src/app/services/api/embassy-api.service'

View File

@@ -1,14 +1,12 @@
import { Component, inject, OnInit, signal } from '@angular/core'
import { ErrorService, LoadingService } from '@start9labs/shared'
import { TuiButton, TuiLink, TuiNotification } from '@taiga-ui/core'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { FormComponent } from 'src/app/routes/portal/components/form.component'
import { ErrorService } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import {
BackupTargetType,
RR,
UnknownDisk,
} from 'src/app/services/api/api.types'
import { TuiButton, TuiLink, TuiNotification } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
import { FormComponent } from 'src/app/routes/portal/components/form.component'
import { RR, UnknownDisk } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { configBuilderToSpec } from 'src/app/utils/configBuilderToSpec'
@@ -22,11 +20,10 @@ import {
googleDriveSpec,
remoteBackupTargetSpec,
} from '../types/target'
import { DocsLinkDirective } from 'projects/shared/src/public-api'
@Component({
template: `
<tui-notification>
<div tuiNotification>
Backup targets are physical or virtual locations for storing encrypted
backups. They can be physical drives plugged into your server, shared
folders on your Local Area Network (LAN), or third party clouds such as
@@ -34,7 +31,7 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api'
<a tuiLink docsLink path="/start-os/backup-create.html">
View instructions
</a>
</tui-notification>
</div>
<h3 class="g-title">
Unknown Physical Drives
<button
@@ -77,7 +74,7 @@ export class BackupsTargetsModal implements OnInit {
private readonly api = inject(ApiService)
private readonly errorService = inject(ErrorService)
private readonly formDialog = inject(FormDialogService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
targets = signal<RR.GetBackupTargetsRes | null>(null)

View File

@@ -1,17 +1,17 @@
import { inject, Injectable } from '@angular/core'
import { LoadingService } from '@start9labs/shared'
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
import { from, switchMap } from 'rxjs'
import { T } from '@start9labs/start-sdk'
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import { from, switchMap } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TARGET, TARGET_CREATE } from '../modals/target.component'
import { BACKUP, BACKUP_OPTIONS } from '../modals/backup.component'
import { TARGET, TARGET_CREATE } from '../modals/target.component'
@Injectable({
providedIn: 'root',
})
export class BackupsCreateService {
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
private readonly dialogs = inject(TuiDialogService)
private readonly api = inject(ApiService)

View File

@@ -1,12 +1,10 @@
import { inject, Injectable } from '@angular/core'
import { Router } from '@angular/router'
import { verify } from '@start9labs/argon2'
import {
ErrorService,
LoadingService,
StartOSDiskInfo,
} from '@start9labs/shared'
import { ErrorService, StartOSDiskInfo } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TuiDialogOptions, TuiDialogService } from '@taiga-ui/core'
import { TuiNotificationMiddleService } from '@taiga-ui/kit'
import {
catchError,
EMPTY,
@@ -22,7 +20,6 @@ import {
PROMPT,
PromptOptions,
} from 'src/app/routes/portal/modals/prompt.component'
import { T } from '@start9labs/start-sdk'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { RECOVER } from '../modals/recover.component'
import { SERVERS } from '../modals/servers.component'
@@ -37,7 +34,7 @@ export class BackupsRestoreService {
private readonly dialogs = inject(TuiDialogService)
private readonly router = inject(Router)
private readonly api = inject(ApiService)
private readonly loader = inject(LoadingService)
private readonly loader = inject(TuiNotificationMiddleService)
readonly handle = () => {
this.dialogs

View File

@@ -1,15 +1,14 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { RouterLink } from '@angular/router'
import { i18nPipe } from '@start9labs/shared'
import { TuiAppearance, TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TuiCardMedium } from '@taiga-ui/layout'
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TitleDirective } from 'src/app/services/title.service'
@Component({
template: `
<ng-container *title>{{ 'Logs' | i18n }}</ng-container>
@for (log of logs; track $index) {
<a tuiCardMedium tuiAppearance="neutral" [routerLink]="log.link">
<a [routerLink]="log.link">
<tui-icon [icon]="log.icon" />
<span tuiTitle>
{{ log.title | i18n }}
@@ -32,7 +31,13 @@ import { TitleDirective } from 'src/app/services/title.service'
padding: 1rem;
}
[tuiCardMedium] {
a {
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 0.75rem;
border-radius: var(--tui-radius-l);
background: var(--tui-background-neutral-1);
height: 14rem;
width: 14rem;
cursor: pointer;
@@ -61,15 +66,7 @@ import { TitleDirective } from 'src/app/services/title.service'
}
`,
],
imports: [
RouterLink,
TitleDirective,
TuiTitle,
TuiCardMedium,
TuiIcon,
TuiAppearance,
i18nPipe,
],
imports: [RouterLink, TitleDirective, TuiTitle, TuiIcon, i18nPipe],
})
export default class SystemLogsComponent {
readonly logs = [

Some files were not shown because too many files have changed in this diff Show More