Update Angular (#2952)

* fix Tor logs actually fetching od logs

* chore: update to Angular 18

* chore: update to Angular 19

* bump patchDB

* chore: update Angular

* chore: fix setup-wizard success page

* chore: fix

* chore: fix

* chore: fix

* chore: fix

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Alex Inkin
2025-05-30 21:34:24 +07:00
committed by GitHub
parent 05b8dd9ad8
commit 02413a4fac
257 changed files with 5921 additions and 9539 deletions

View File

@@ -26,6 +26,7 @@ import { PatchMonitorService } from './services/patch-monitor.service'
font-family: 'Proxima Nova', sans-serif;
}
`,
standalone: false,
})
export class AppComponent {
private readonly i18n = inject(i18nService)

View File

@@ -1,4 +1,4 @@
import { HttpClientModule } from '@angular/common/http'
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { NgModule } from '@angular/core'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { ServiceWorkerModule } from '@angular/service-worker'
@@ -12,7 +12,6 @@ import { RoutingModule } from './routing.module'
@NgModule({
declarations: [AppComponent],
imports: [
HttpClientModule,
BrowserAnimationsModule,
RoutingModule,
ToastContainerComponent,
@@ -24,7 +23,7 @@ import { RoutingModule } from './routing.module'
registrationStrategy: 'registerWhenStable:30000',
}),
],
providers: APP_PROVIDERS,
providers: [APP_PROVIDERS, provideHttpClient(withInterceptorsFromDi())],
bootstrap: [AppComponent],
})
export class AppModule {}

View File

@@ -1,6 +1,8 @@
import { APP_INITIALIZER, inject, Provider } from '@angular/core'
import { inject, provideAppInitializer } from '@angular/core'
import { UntypedFormBuilder } from '@angular/forms'
import { Router } from '@angular/router'
import { WA_LOCATION } from '@ng-web-apis/common'
import initArgon from '@start9labs/argon2'
import {
AbstractCategoryService,
FilterPackagesPipe,
@@ -50,7 +52,7 @@ const {
ui: { api },
} = require('../../../../config.json') as WorkspaceConfig
export const APP_PROVIDERS: Provider[] = [
export const APP_PROVIDERS = [
provideEventPlugins(),
I18N_PROVIDERS,
FilterPackagesPipe,
@@ -86,11 +88,18 @@ export const APP_PROVIDERS: Provider[] = [
deps: [PatchDbSource, PATCH_CACHE],
useClass: PatchDB,
},
{
provide: APP_INITIALIZER,
useFactory: appInitializer,
multi: true,
},
provideAppInitializer(() => {
const i18n = inject(i18nService)
const origin = inject(WA_LOCATION).origin
const module_or_path = new URL('/assets/argon2_bg.wasm', origin)
initArgon({ module_or_path })
inject(StorageService).migrate036()
inject(AuthService).init()
inject(ClientStorageService).init()
inject(Router).initialNavigation()
i18n.setLanguage(i18n.language || 'english')
}),
{
provide: RELATIVE_URL,
useValue: `/${api.url}/${api.version}`,
@@ -123,19 +132,3 @@ export const APP_PROVIDERS: Provider[] = [
useFactory: () => inject(ConfigService).version,
},
]
export function appInitializer(): () => void {
const storage = inject(StorageService)
const auth = inject(AuthService)
const localStorage = inject(ClientStorageService)
const router = inject(Router)
const i18n = inject(i18nService)
return () => {
storage.migrate036()
auth.init()
localStorage.init()
router.initialNavigation()
i18n.setLanguage(i18n.language || 'english')
}
}

View File

@@ -45,7 +45,6 @@ import { BackupReport } from 'src/app/services/api/api.types'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, TuiIcon, TuiCell, TuiTitle, i18nPipe],
})
export class BackupsReportModal {

View File

@@ -8,7 +8,6 @@ import { endWith, map, merge, Observable, pairwise, Subject } from 'rxjs'
import { DataModel } from 'src/app/services/patch-db/data-model'
@Component({
standalone: true,
selector: 'notifications-toast',
template: `
<ng-template

View File

@@ -13,7 +13,6 @@ import { ConfigService } from 'src/app/services/config.service'
import { DataModel } from 'src/app/services/patch-db/data-model'
@Component({
standalone: true,
selector: 'refresh-alert',
template: `
<ng-template

View File

@@ -3,7 +3,6 @@ import { NotificationsToastComponent } from './notifications-toast.component'
import { RefreshAlertComponent } from './refresh-alert.component'
@Component({
standalone: true,
selector: 'toast-container',
template: `
<notifications-toast />

View File

@@ -9,6 +9,7 @@ import { ConfigService } from 'src/app/services/config.service'
selector: 'diagnostic-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
standalone: false,
})
export class HomePage {
restarted = false

View File

@@ -8,11 +8,10 @@ import { WaMutationObserver } from '@ng-web-apis/mutation-observer'
import { convertAnsi, ErrorService } from '@start9labs/shared'
import { tuiProvide } from '@taiga-ui/cdk'
import { TuiButton, TuiLoader, TuiScrollbar } from '@taiga-ui/core'
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
standalone: true,
template: `
<a
routerLink="../"
@@ -48,12 +47,16 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
gap: 1rem;
background: var(--tui-background-base);
}
pre {
white-space: normal;
}
`,
imports: [
RouterLink,
WaIntersectionObserver,
WaMutationObserver,
NgDompurifyModule,
NgDompurifyPipe,
TuiButton,
TuiLoader,
TuiScrollbar,

View File

@@ -11,7 +11,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { StateService } from 'src/app/services/state.service'
@Component({
standalone: true,
template: `
<app-initializing [progress]="progress()" />
`,

View File

@@ -7,7 +7,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ConfigService } from 'src/app/services/config.service'
@Component({
standalone: true,
selector: 'ca-wizard',
templateUrl: './ca-wizard.component.html',
styleUrls: ['./ca-wizard.component.scss'],

View File

@@ -1,7 +1,7 @@
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
.card {
@include center-all();
@include taiga.center-all();
overflow: visible;
align-items: center;
text-align: center;
@@ -11,7 +11,7 @@
}
.logo {
@include center-left();
@include taiga.center-left();
top: -17%;
width: 6rem;
}

View File

@@ -12,6 +12,7 @@ import { DOCUMENT } from '@angular/common'
templateUrl: './login.page.html',
styleUrls: ['./login.page.scss'],
providers: [],
standalone: false,
})
export class LoginPage {
password = ''

View File

@@ -35,7 +35,6 @@ export interface FormContext<T> {
}
@Component({
standalone: true,
selector: 'app-form',
template: `
<form
@@ -71,22 +70,20 @@ export interface FormContext<T> {
</footer>
</form>
`,
styles: [
`
footer {
position: sticky;
bottom: 0;
z-index: 10;
display: flex;
justify-content: flex-end;
padding: 1rem 0;
margin: 1rem -1px -1rem;
gap: 1rem;
background: var(--tui-background-elevation-1);
border-top: 1px solid var(--tui-background-base-alt);
}
`,
],
styles: `
footer {
position: sticky;
bottom: 0;
z-index: 10;
display: flex;
justify-content: flex-end;
padding: 1rem 0;
margin: 1rem -1px -1rem;
gap: 1rem;
background: var(--tui-background-elevation-1);
border-top: 1px solid var(--tui-background-base-alt);
}
`,
imports: [
CommonModule,
ReactiveFormsModule,

View File

@@ -4,6 +4,7 @@ import { InvalidService } from './invalid.service'
@Directive({
selector: 'form-control, form-array, form-object',
standalone: false,
})
export class ControlDirective implements OnInit, OnDestroy {
private readonly invalidService = inject(InvalidService, { optional: true })

View File

@@ -4,6 +4,7 @@ import { KeyValue } from '@angular/common'
@Pipe({
name: 'filterHidden',
standalone: false,
})
export class FilterHiddenPipe implements PipeTransform {
transform(value: KeyValue<string, IST.ValueSpec>[]) {

View File

@@ -1,4 +1,4 @@
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
:host {
display: block;
@@ -27,7 +27,7 @@
}
&::after {
@include transition(opacity);
@include taiga.transition(opacity);
content: '';
position: absolute;

View File

@@ -25,6 +25,7 @@ import { DialogService, i18nKey } from '@start9labs/shared'
templateUrl: './form-array.component.html',
styleUrls: ['./form-array.component.scss'],
animations: [tuiFadeIn, tuiHeightCollapse, tuiParentStop],
standalone: false,
})
export class FormArrayComponent {
@Input({ required: true })

View File

@@ -1,4 +1,4 @@
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
.wrapper {
position: relative;
@@ -19,12 +19,12 @@
}
.color {
@include fullsize();
@include taiga.fullsize();
opacity: 0;
}
.icon {
@include fullsize();
@include taiga.fullsize();
pointer-events: none;
input:hover + & {

View File

@@ -7,6 +7,7 @@ import { MaskitoOptions } from '@maskito/core'
selector: 'form-color',
templateUrl: './form-color.component.html',
styleUrls: ['./form-color.component.scss'],
standalone: false,
})
export class FormColorComponent extends Control<IST.ValueSpecColor, string> {
readonly mask: MaskitoOptions = {

View File

@@ -21,6 +21,7 @@ import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared'
styleUrls: ['./form-control.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: FORM_CONTROL_PROVIDERS,
standalone: false,
})
export class FormControlComponent<
T extends Exclude<IST.ValueSpec, IST.ValueSpecHidden>,

View File

@@ -12,6 +12,7 @@ import { Control } from '../control'
@Component({
selector: 'form-datetime',
templateUrl: './form-datetime.component.html',
standalone: false,
})
export class FormDatetimeComponent extends Control<
IST.ValueSpecDatetime,

View File

@@ -1,7 +1,7 @@
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
.template {
@include transition(opacity);
@include taiga.transition(opacity);
width: 100%;
display: flex;
@@ -16,8 +16,8 @@
}
.drop {
@include fullsize();
@include transition(opacity);
@include taiga.fullsize();
@include taiga.transition(opacity);
display: flex;
align-items: center;
justify-content: space-around;

View File

@@ -7,6 +7,7 @@ import { Control } from '../control'
selector: 'form-file',
templateUrl: './form-file.component.html',
styleUrls: ['./form-file.component.scss'],
standalone: false,
})
export class FormFileComponent extends Control<
IST.ValueSpecFile,

View File

@@ -25,6 +25,7 @@ export const ERRORS = [
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
viewProviders: [FORM_GROUP_PROVIDERS],
standalone: false,
})
export class FormGroupComponent {
@Input() spec: IST.InputSpec = {}

View File

@@ -7,6 +7,7 @@ import { invert } from '@start9labs/shared'
@Component({
selector: 'form-multiselect',
templateUrl: './form-multiselect.component.html',
standalone: false,
})
export class FormMultiselectComponent extends Control<
IST.ValueSpecMultiselect,

View File

@@ -5,6 +5,7 @@ import { Control } from '../control'
@Component({
selector: 'form-number',
templateUrl: './form-number.component.html',
standalone: false,
})
export class FormNumberComponent extends Control<IST.ValueSpecNumber, number> {
protected readonly Infinity = Infinity

View File

@@ -1,4 +1,4 @@
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
:host {
display: flex;
@@ -18,7 +18,7 @@
}
.button {
@include transition(transform);
@include taiga.transition(transform);
margin-right: 1rem;

View File

@@ -14,6 +14,7 @@ import { IST } from '@start9labs/start-sdk'
templateUrl: './form-object.component.html',
styleUrls: ['./form-object.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false,
})
export class FormObjectComponent {
@Input({ required: true })

View File

@@ -6,6 +6,7 @@ import { Control } from '../control'
@Component({
selector: 'form-select',
templateUrl: './form-select.component.html',
standalone: false,
})
export class FormSelectComponent extends Control<IST.ValueSpecSelect, string> {
private readonly inverted = invert(this.spec.values)

View File

@@ -6,6 +6,7 @@ import { Control } from '../control'
selector: 'form-text',
templateUrl: './form-text.component.html',
styleUrls: ['./form-text.component.scss'],
standalone: false,
})
export class FormTextComponent extends Control<IST.ValueSpecText, string> {
masked = true

View File

@@ -5,6 +5,7 @@ import { Control } from '../control'
@Component({
selector: 'form-textarea',
templateUrl: './form-textarea.component.html',
standalone: false,
})
export class FormTextareaComponent extends Control<
IST.ValueSpecTextarea,

View File

@@ -6,6 +6,7 @@ import { Control } from '../control'
selector: 'form-toggle',
templateUrl: './form-toggle.component.html',
host: { class: 'g-toggle' },
standalone: false,
})
export class FormToggleComponent extends Control<
IST.ValueSpecToggle,

View File

@@ -21,6 +21,7 @@ import { tuiPure } from '@taiga-ui/cdk'
useExisting: FormGroupName,
},
],
standalone: false,
})
export class FormUnionComponent implements OnChanges {
@Input({ required: true })

View File

@@ -4,6 +4,7 @@ import { IST } from '@start9labs/start-sdk'
@Pipe({
name: 'hint',
standalone: false,
})
export class HintPipe implements PipeTransform {
private readonly i18n = inject(i18nPipe)

View File

@@ -1,9 +1,9 @@
import { Pipe, PipeTransform } from '@angular/core'
const Mustache = require('mustache')
import Mustache from 'mustache'
@Pipe({
name: 'mustache',
standalone: false,
})
export class MustachePipe implements PipeTransform {
transform(value: any, displayAs: string): string {

View File

@@ -50,7 +50,6 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
`,
styles: '[tuiCell] { padding-inline: 0; white-space: nowrap }',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [TuiTitle, TuiButton, TuiCell, i18nPipe, TuiFade],
})
export class AboutComponent {

View File

@@ -21,107 +21,117 @@ import { HeaderStatusComponent } from './status.component'
<header-status class="item item_connection" />
<header-menu class="item item_corner" />
`,
styles: [
`
@import '@taiga-ui/core/styles/taiga-ui-local';
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
:host {
display: flex;
height: 2.75rem;
border-radius: var(--bumper);
margin: var(--bumper);
overflow: hidden;
filter: grayscale(1) brightness(0.75);
@keyframes connecting {
25%,
100% {
background-position: 175%;
}
}
@include transition(filter);
:host {
display: flex;
height: 2.75rem;
border-radius: var(--bumper);
margin: var(--bumper);
overflow: hidden;
filter: grayscale(1) brightness(0.75);
.mobile {
display: none;
@include taiga.transition(filter);
.mobile {
display: none;
}
.item {
position: relative;
border-radius: inherit;
isolation: isolate;
&::before {
@include taiga.transition(all);
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
backdrop-filter: blur(1rem);
transform: skewX(30deg);
background: color-mix(in hsl, var(--start9-base-2) 75%, transparent);
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
z-index: -1;
}
.item {
position: relative;
border-radius: inherit;
isolation: isolate;
&_center {
flex: 1;
min-width: 0;
}
&_connection::before {
box-shadow:
inset 0 1px rgba(255, 255, 255, 0.25),
inset 0 -0.75rem 0 -0.5rem var(--status);
background-image: linear-gradient(
90deg,
transparent 35%,
rgba(255, 255, 255, 0.25),
transparent 65%
);
background-size: 50% 100%;
background-position: -75%;
background-repeat: no-repeat;
animation: connecting 2s 2s ease-in-out infinite;
}
&_corner {
margin-inline-start: var(--bumper);
&::before {
@include transition(all);
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
backdrop-filter: blur(1rem);
transform: skewX(30deg);
background: color-mix(
in hsl,
var(--start9-base-2) 75%,
transparent
);
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
z-index: -1;
}
&_center {
flex: 1;
min-width: 0;
}
&_connection::before {
box-shadow:
inset 0 1px rgba(255, 255, 255, 0.25),
inset 0 -0.75rem 0 -0.5rem var(--status);
}
&_corner {
margin-inline-start: var(--bumper);
&::before {
right: -2rem;
}
}
}
&:has([data-status='error']) {
--status: var(--tui-status-negative);
}
&:has([data-status='warning']) {
--status: var(--tui-status-warning);
}
&:has([data-status='neutral']) {
--status: var(--tui-status-neutral);
filter: none;
}
&:has([data-status='success']) {
--status: transparent;
filter: none;
}
}
:host-context(tui-root._mobile) {
.item_center::before {
left: -2rem;
}
.mobile {
display: flex;
height: 100%;
align-items: center;
font: var(--tui-font-text-l);
padding: 1rem;
white-space: nowrap;
overflow: hidden;
::ng-deep > [tuiIconButton] {
margin-inline-start: -1rem;
right: -2rem;
}
}
}
`,
],
standalone: true,
&:has([data-status='error']) {
--status: var(--tui-status-negative);
}
&:has([data-status='warning']) {
--status: var(--tui-status-warning);
}
&:has([data-status='neutral']) {
--status: var(--tui-status-neutral);
filter: none;
}
&:has([data-status='success']) {
--status: transparent;
filter: none;
}
}
:host-context(tui-root._mobile) {
.item_center::before {
left: -2rem;
}
.mobile {
display: flex;
height: 100%;
align-items: center;
font: var(--tui-font-text-l);
padding: 1rem;
white-space: nowrap;
overflow: hidden;
::ng-deep > [tuiIconButton] {
margin-inline-start: -1rem;
}
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
HeaderStatusComponent,

View File

@@ -100,33 +100,30 @@ import { ABOUT } from './about.component'
</tui-data-list>
</ng-template>
`,
styles: [
`
:host {
padding-inline-start: 0.5rem;
styles: `
:host {
padding-inline-start: 0.5rem;
&._open::before {
filter: brightness(1.2);
}
&._open::before {
filter: brightness(1.2);
}
}
.status {
display: flex;
gap: 1rem;
align-items: center;
padding: 1rem 1rem 0.5rem;
opacity: 0.5;
}
.status {
display: flex;
gap: 1rem;
align-items: center;
padding: 1rem 1rem 0.5rem;
opacity: 0.5;
}
:host-context(tui-root._mobile) {
[tuiIconButton] {
box-shadow: inset -1.25rem 0 0 -1rem var(--status);
}
:host-context(tui-root._mobile) {
[tuiIconButton] {
box-shadow: inset -1.25rem 0 0 -1rem var(--status);
}
`,
],
}
`,
host: { '[class._open]': 'open' },
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
TuiDropdown,

View File

@@ -14,7 +14,6 @@ import { TuiBadgedContent, TuiBadgeNotification } from '@taiga-ui/kit'
import { getMenu } from 'src/app/utils/system-utilities'
@Component({
standalone: true,
selector: 'header-navigation',
template: `
@for (item of utils; track $index) {
@@ -45,139 +44,137 @@ import { getMenu } from 'src/app/utils/system-utilities'
</a>
}
`,
styles: [
`
@import '@taiga-ui/core/styles/taiga-ui-local';
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
:host {
:host {
position: relative;
display: flex;
border-radius: inherit;
margin-inline-end: 0.875rem;
isolation: isolate;
&::before {
content: '';
position: absolute;
top: 0;
left: -1rem;
right: -0.5rem;
bottom: 0;
transform: skewX(30deg);
border-radius: var(--bumper);
z-index: -1;
backdrop-filter: blur(1rem);
}
}
.link {
@include taiga.transition(all);
position: relative;
display: grid;
grid-template-columns: 1.5rem 0fr;
align-items: center;
padding: 0 0.5rem;
margin: 0;
border-radius: inherit;
color: var(--tui-text-secondary);
&:not(.link_active):hover tui-icon {
transform: scale(1.1);
}
&:not(.link_active):active tui-icon {
transform: scale(0.8);
}
&::before {
@include taiga.transition(all);
content: '';
position: absolute;
inset: 0;
transform: skewX(30deg);
background: color-mix(in hsl, var(--start9-base-2) 75%, transparent);
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
z-index: -1;
}
span {
@include taiga.transition(opacity);
position: relative;
display: flex;
border-radius: inherit;
margin-inline-end: 0.875rem;
isolation: isolate;
overflow: hidden;
text-indent: 0.5rem;
opacity: 0;
}
&::before {
content: '';
position: absolute;
top: 0;
left: -1rem;
right: -0.5rem;
bottom: 0;
transform: skewX(30deg);
border-radius: var(--bumper);
z-index: -1;
backdrop-filter: blur(1rem);
&:hover,
&_active {
color: var(--tui-text-primary);
tui-icon {
color: var(--tui-text-primary);
}
}
.link {
@include transition(all);
position: relative;
display: grid;
grid-template-columns: 1.5rem 0fr;
align-items: center;
padding: 0 0.5rem;
margin: 0;
border-radius: inherit;
color: var(--tui-text-secondary);
&_active {
grid-template-columns: 1.5rem 1fr;
padding: 0 1rem;
margin: 0 calc(var(--bumper) + 0.5rem);
&:not(.link_active):hover tui-icon {
transform: scale(1.1);
&.link_system {
pointer-events: none;
}
&:not(.link_active):active tui-icon {
transform: scale(0.8);
+ .link::before {
left: -0.5rem;
border-top-left-radius: var(--bumper);
border-bottom-left-radius: var(--bumper);
}
&::before {
@include transition(all);
content: '';
position: absolute;
inset: 0;
transform: skewX(30deg);
background: color-mix(in hsl, var(--start9-base-2) 75%, transparent);
box-shadow: inset 0 1px rgb(255 255 255 / 25%);
z-index: -1;
border-radius: var(--bumper);
filter: brightness(0.5);
}
span {
@include transition(opacity);
position: relative;
overflow: hidden;
text-indent: 0.5rem;
opacity: 0;
opacity: 1;
}
}
&:hover,
&_active {
color: var(--tui-text-primary);
&:has(+ .link_active)::before {
right: -0.5rem;
border-top-right-radius: var(--bumper);
border-bottom-right-radius: var(--bumper);
}
tui-icon {
color: var(--tui-text-primary);
}
&:first-child {
padding: 0 0.5rem 0 1rem !important;
margin-inline-start: 0;
&::before {
left: -2rem;
}
}
&_active {
grid-template-columns: 1.5rem 1fr;
padding: 0 1rem;
margin: 0 calc(var(--bumper) + 0.5rem);
&:last-child {
margin-inline-end: 0;
&.link_system {
pointer-events: none;
}
+ .link::before {
left: -0.5rem;
border-top-left-radius: var(--bumper);
border-bottom-left-radius: var(--bumper);
}
&::before {
border-radius: var(--bumper);
filter: brightness(0.5);
}
span {
opacity: 1;
}
}
&:has(+ .link_active)::before {
&::before {
right: -0.5rem;
border-top-right-radius: var(--bumper);
border-bottom-right-radius: var(--bumper);
}
&:first-child {
padding: 0 0.5rem 0 1rem !important;
margin-inline-start: 0;
&::before {
left: -2rem;
}
}
&:last-child {
margin-inline-end: 0;
&::before {
right: -0.5rem;
border-top-right-radius: inherit;
border-bottom-right-radius: inherit;
}
border-top-right-radius: inherit;
border-bottom-right-radius: inherit;
}
}
}
tui-icon {
@include transition(transform);
color: var(--tui-text-secondary);
}
tui-icon {
@include taiga.transition(transform);
color: var(--tui-text-secondary);
}
:host-context(tui-root._mobile) {
display: none;
}
`,
],
:host-context(tui-root._mobile) {
display: none;
}
`,
animations: [tuiFadeIn, tuiWidthCollapse, tuiScaleIn],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [

View File

@@ -4,7 +4,6 @@ import { TuiIcon } from '@taiga-ui/core'
import { STATUS } from 'src/app/services/status.service'
@Component({
standalone: true,
selector: 'header-status',
template: `
<span>
@@ -18,34 +17,32 @@ import { STATUS } from 'src/app/services/status.service'
</span>
<span>{{ status().message | i18n }}</span>
`,
styles: [
`
@import '@taiga-ui/core/styles/taiga-ui-local';
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
:host {
@include transition(all);
display: grid;
grid-template-columns: 1.75rem 1fr;
align-items: center;
padding: 0 1rem;
margin-inline-start: var(--bumper);
:host {
@include taiga.transition(all);
display: grid;
grid-template-columns: 1.75rem 1fr;
align-items: center;
padding: 0 1rem;
margin-inline-start: var(--bumper);
&._connected {
grid-template-columns: 0fr 0fr;
padding: 0;
margin: 0;
}
> * {
overflow: hidden;
}
&._connected {
grid-template-columns: 0fr 0fr;
padding: 0;
margin: 0;
}
:host-context(tui-root._mobile) {
display: none;
> * {
overflow: hidden;
}
`,
],
}
:host-context(tui-root._mobile) {
display: none;
}
`,
host: { '[class._connected]': 'status().status === "success"' },
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiIcon, i18nPipe],

View File

@@ -2,7 +2,6 @@ import { Pipe, PipeTransform } from '@angular/core'
import { toAcmeName } from 'src/app/utils/acme'
@Pipe({
standalone: true,
name: 'acme',
})
export class AcmePipe implements PipeTransform {

View File

@@ -18,7 +18,6 @@ import { InterfaceComponent } from './interface.component'
import { DOCUMENT } from '@angular/common'
@Component({
standalone: true,
selector: 'td[actions]',
template: `
<div class="desktop">

View File

@@ -47,7 +47,6 @@ type ClearnetForm = {
}
@Component({
standalone: true,
selector: 'section[clearnet]',
template: `
<header>

View File

@@ -13,7 +13,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { MappedServiceInterface } from './interface.utils'
@Component({
standalone: true,
selector: 'app-interface',
template: `
<button
@@ -44,6 +43,10 @@ import { MappedServiceInterface } from './interface.utils'
gap: 1rem;
color: var(--tui-text-secondary);
font: var(--tui-font-text-l);
::ng-deep td {
overflow-wrap: anywhere;
}
}
button {

View File

@@ -8,7 +8,6 @@ import { MaskPipe } from './mask.pipe'
import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
@Component({
standalone: true,
selector: 'section[local]',
template: `
<header>

View File

@@ -2,7 +2,6 @@ import { inject, Pipe, PipeTransform } from '@angular/core'
import { InterfaceComponent } from './interface.component'
@Pipe({
standalone: true,
name: 'mask',
})
export class MaskPipe implements PipeTransform {
@@ -10,7 +9,7 @@ export class MaskPipe implements PipeTransform {
transform(value: string): string {
return this.interface.value().masked
? ''.repeat(Math.min(64, value.length))
? ''.repeat(Math.min(32, value.length))
: value
}
}

View File

@@ -3,7 +3,6 @@ import { i18nPipe } from '@start9labs/shared'
import { TuiBadge } from '@taiga-ui/kit'
@Component({
standalone: true,
selector: 'interface-status',
template: `
<tui-badge

View File

@@ -19,7 +19,7 @@ import {
TuiLink,
TuiOption,
} from '@taiga-ui/core'
import { TuiFade, TuiFluidTypography, TuiTooltip } from '@taiga-ui/kit'
import { TuiTooltip } from '@taiga-ui/kit'
import { defaultIfEmpty, firstValueFrom } from 'rxjs'
import {
FormComponent,
@@ -40,7 +40,6 @@ type OnionForm = {
}
@Component({
standalone: true,
selector: 'section[tor]',
template: `
<header>
@@ -71,11 +70,7 @@ type OnionForm = {
@for (address of tor(); track $index) {
<tr>
<td [style.width.rem]="12">{{ address.protocol || '-' }}</td>
<td>
<div [tuiFluidTypography]="[0.625, 0.8125]" tuiFade>
{{ address.url | mask }}
</div>
</td>
<td>{{ address.url | mask }}</td>
<td actions [href]="address.url" [disabled]="!isRunning()">
<button
tuiIconButton
@@ -124,8 +119,6 @@ type OnionForm = {
PlaceholderComponent,
MaskPipe,
InterfaceActionsComponent,
TuiFade,
TuiFluidTypography,
i18nPipe,
DocsLinkDirective,
],

View File

@@ -10,7 +10,6 @@ import {
import { LogsComponent } from './logs.component'
@Directive({
standalone: true,
selector: 'button[logsDownload]',
})
export class LogsDownloadDirective {

View File

@@ -5,7 +5,6 @@ import { catchError, defer, filter, from, map, of, switchMap, tap } from 'rxjs'
import { LogsComponent } from './logs.component'
@Directive({
standalone: true,
selector: '[logsFetch]',
})
export class LogsFetchDirective {

View File

@@ -47,4 +47,5 @@
pre {
overflow: visible;
white-space: normal;
}

View File

@@ -6,16 +6,15 @@ import {
} from '@ng-web-apis/intersection-observer'
import { WaMutationObserver } from '@ng-web-apis/mutation-observer'
import { FetchLogsReq, FetchLogsRes, i18nPipe } from '@start9labs/shared'
import { TuiLoader, TuiScrollbar, TuiButton } from '@taiga-ui/core'
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
import { TuiButton, TuiLoader, TuiScrollbar } from '@taiga-ui/core'
import { NgDompurifyPipe } from '@taiga-ui/dompurify'
import { BehaviorSubject } from 'rxjs'
import { RR } from 'src/app/services/api/api.types'
import { LogsDownloadDirective } from './logs-download.directive'
import { LogsFetchDirective } from './logs-fetch.directive'
import { LogsPipe } from './logs.pipe'
import { BehaviorSubject } from 'rxjs'
@Component({
standalone: true,
selector: 'logs',
templateUrl: './logs.component.html',
styleUrls: ['./logs.component.scss'],
@@ -23,7 +22,7 @@ import { BehaviorSubject } from 'rxjs'
CommonModule,
WaIntersectionObserver,
WaMutationObserver,
NgDompurifyModule,
NgDompurifyPipe,
TuiButton,
TuiLoader,
TuiScrollbar,

View File

@@ -29,7 +29,6 @@ import { LogsComponent } from './logs.component'
@Pipe({
name: 'logs',
standalone: true,
})
export class LogsPipe implements PipeTransform {
private readonly api = inject(ApiService)

View File

@@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'
import { TuiIcon } from '@taiga-ui/core'
@Component({
standalone: true,
selector: 'app-placeholder',
template: '<tui-icon [icon]="icon()" /><ng-content/>',
styles: `

View File

@@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'
import { i18nKey, i18nPipe } from '@start9labs/shared'
@Component({
standalone: true,
selector: 'table[appTable]',
template: `
<thead>

View File

@@ -17,7 +17,6 @@ import { getMenu } from 'src/app/utils/system-utilities'
const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
@Component({
standalone: true,
selector: 'app-tabs',
template: `
<nav tuiTabBar [(activeItemIndex)]="index">
@@ -76,7 +75,7 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
</nav>
`,
styles: `
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
:host {
display: none;
@@ -93,7 +92,7 @@ const FILTER = ['/portal/services', '/portal/system', '/portal/marketplace']
}
.item {
@include button-clear();
@include taiga.button-clear();
display: flex;
padding: 0.75rem 0.25rem;

View File

@@ -1,62 +0,0 @@
import { Component, Input, OnChanges, OnDestroy } from '@angular/core'
import { tuiInjectElement } from '@taiga-ui/cdk'
@Component({
standalone: true,
selector: '[appUptime]',
template: '',
styles: `
:host {
&::before {
content: 'Uptime: ';
}
&:empty::after {
content: '-';
}
}
:host-context(tui-root._mobile) {
grid-row: 2;
margin: 0;
font: var(--tui-font-text-ui-s);
}
`,
})
export class UptimeComponent implements OnChanges, OnDestroy {
private readonly el = tuiInjectElement()
private interval: any = NaN
@Input()
appUptime = ''
ngOnChanges() {
clearInterval(this.interval)
if (!this.appUptime) {
this.el.textContent = ''
} else {
this.el.textContent = uptime(new Date(this.appUptime))
this.interval = setInterval(() => {
this.el.textContent = uptime(new Date(this.appUptime))
}, 60 * 1000)
}
}
ngOnDestroy() {
clearInterval(this.interval)
}
}
function uptime(date: Date): string {
const delta = Date.now() - date.getTime()
const days = Math.floor(delta / (1000 * 60 * 60 * 24))
const hours = Math.floor((delta / (1000 * 60 * 60)) % 24)
const minutes = Math.floor((delta / (1000 * 60)) % 60)
if (days > 0) return `${days}d ${hours}h ${minutes}m`
if (hours > 0) return `${hours}h ${minutes}m`
return `${minutes}m`
}

View File

@@ -21,16 +21,13 @@ import { CommonModule } from '@angular/common'
</ul>
</tui-notification>
`,
standalone: true,
imports: [CommonModule, TuiNotification, i18nPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
styles: [
`
tui-notification {
margin-bottom: 1.5rem;
}
`,
],
styles: `
tui-notification {
margin-bottom: 1.5rem;
}
`,
})
export class TaskInfoComponent implements OnInit {
@Input()

View File

@@ -4,7 +4,6 @@ import { injectContext } from '@taiga-ui/polymorpheus'
import { QrCodeModule } from 'ng-qrcode'
@Component({
standalone: true,
selector: 'qr',
template: '<qr-code [value]="context.data" size="350"></qr-code>',
changeDetection: ChangeDetectionStrategy.OnPush,

View File

@@ -4,7 +4,6 @@ import { getManifest } from 'src/app/utils/get-package-data'
@Pipe({
name: 'toManifest',
standalone: true,
})
export class ToManifestPipe implements PipeTransform {
transform(pkg: PackageDataEntry) {

View File

@@ -17,7 +17,6 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
import { HeaderComponent } from './components/header/header.component'
@Component({
standalone: true,
template: `
<header appHeader>{{ name() }}</header>
<main>
@@ -27,7 +26,7 @@ import { HeaderComponent } from './components/header/header.component'
</main>
<app-tabs />
@if (update(); as update) {
<tui-action-bar *tuiActionBar="true">
<tui-action-bar *tuiActionBar="bar">
@if (update === true) {
<tui-icon icon="@tui.check" class="g-positive" />
Download complete, restart to apply changes
@@ -52,40 +51,38 @@ import { HeaderComponent } from './components/header/header.component'
</tui-action-bar>
}
`,
styles: [
`
@import '@taiga-ui/core/styles/taiga-ui-local';
styles: `
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
:host {
height: 100%;
display: flex;
flex-direction: column;
// @TODO Theme
background: url(/assets/img/background_dark.jpeg) fixed center/cover;
:host {
height: 100%;
display: flex;
flex-direction: column;
// @TODO Theme
background: url(/assets/img/background_dark.jpeg) fixed center/cover;
&::before {
content: '';
position: fixed;
inset: 0;
backdrop-filter: blur(0.5rem);
}
&::before {
content: '';
position: fixed;
inset: 0;
backdrop-filter: blur(0.5rem);
}
}
main {
flex: 1;
overflow: hidden;
margin: 0 var(--bumper) var(--bumper);
filter: grayscale(1) brightness(0.75);
main {
flex: 1;
overflow: hidden;
margin: 0 var(--bumper) var(--bumper);
filter: grayscale(1) brightness(0.75);
@include transition(filter);
@include taiga.transition(filter);
header:has([data-status='success']) + &,
header:has([data-status='neutral']) + & {
filter: none;
}
header:has([data-status='success']) + &,
header:has([data-status='neutral']) + & {
filter: none;
}
`,
],
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
RouterOutlet,
@@ -107,6 +104,7 @@ export class PortalComponent {
readonly name = toSignal(this.patch.watch$('ui', 'name'))
readonly update = toSignal(inject(OSService).updating$)
bar = true
getProgress(size: number, downloaded: number): number {
return Math.round((100 * downloaded) / (size || 1))
@@ -116,6 +114,7 @@ export class PortalComponent {
const loader = this.loader.open('Beginning restart').subscribe()
try {
this.bar = false
await this.api.restartServer({})
} catch (e: any) {
this.errorService.handleError(e)

View File

@@ -27,7 +27,6 @@ import { BackupsRestoreService } from './services/restore.service'
`,
host: { class: 'g-page' },
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [BackupsUpcomingComponent, TuiIcon, TuiCell, TuiTitle],
})
export default class BackupsComponent {

View File

@@ -109,7 +109,6 @@ import { UnknownDisk } from 'src/app/services/api/api.types'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [TuiButton, UnitConversionPipesModule, TuiSkeleton],
})
export class BackupsPhysicalComponent {

View File

@@ -8,9 +8,14 @@ import { BackupType } from '../types/backup-type'
<tui-icon [icon]="status.icon" [style.color]="status.color" />
{{ status.text }}
`,
styles: [':host { display: flex; gap: 0.5rem; align-items: center }'],
styles: `
:host {
display: flex;
gap: 0.5rem;
align-items: center;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [TuiIcon],
})
export class BackupsStatusComponent {

View File

@@ -132,7 +132,6 @@ import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [TuiButton, GetBackupIconPipe, TuiIcon, KeyValuePipe, TuiSkeleton],
})
export class BackupsTargetsComponent {

View File

@@ -93,7 +93,6 @@ import { GetBackupIconPipe } from '../pipes/get-backup-icon.pipe'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [GetBackupIconPipe, DatePipe, TuiIcon, TuiSkeleton],
})
export class BackupsUpcomingComponent {

View File

@@ -57,21 +57,18 @@ interface Package {
</button>
</footer>
`,
styles: [
`
:host {
display: flex;
flex-direction: column;
margin-top: 1.5rem;
}
styles: `
:host {
display: flex;
flex-direction: column;
margin-top: 1.5rem;
}
.icon {
width: 2.5rem;
border-radius: 100%;
}
`,
],
standalone: true,
.icon {
width: 2.5rem;
border-radius: 100%;
}
`,
imports: [FormsModule, TuiButton, TuiGroup, TuiLoader, TuiBlock, TuiCheckbox],
})
export class BackupsBackupModal {

View File

@@ -81,23 +81,20 @@ import { TARGET, TARGET_CREATE } from './target.component'
</button>
</form>
`,
styles: [
`
.form {
display: flex;
flex-direction: column;
gap: 1rem;
}
styles: `
.form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.button[data-size] {
width: unset;
padding: 1rem;
text-indent: 0;
justify-content: space-between;
}
`,
],
standalone: true,
.button[data-size] {
width: unset;
padding: 1rem;
text-indent: 0;
justify-content: space-between;
}
`,
imports: [
CommonModule,
FormsModule,

View File

@@ -101,7 +101,7 @@ import { HasErrorPipe } from '../pipes/has-error.pipe'
</table>
`,
styles: `
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
tui-icon {
font-size: 1rem;
@@ -127,10 +127,10 @@ import { HasErrorPipe } from '../pipes/has-error.pipe'
}
.checkbox {
@include fullsize();
@include taiga.fullsize();
[tuiCheckbox] {
@include fullsize();
@include taiga.fullsize();
opacity: 0;
}
}
@@ -153,7 +153,6 @@ import { HasErrorPipe } from '../pipes/has-error.pipe'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
CommonModule,
FormsModule,

View File

@@ -132,7 +132,6 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api'
}
}
`,
standalone: true,
imports: [
TuiNotification,
TuiButton,

View File

@@ -59,7 +59,6 @@ import { RecoverOption } from '../types/recover-option'
</ng-container>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
CommonModule,
FormsModule,

View File

@@ -8,7 +8,6 @@ interface Data {
}
@Component({
standalone: true,
template: `
@for (server of context.data.servers; track $index) {
<button

View File

@@ -61,7 +61,6 @@ import { TARGETS } from './targets.component'
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
TuiLoader,
TuiButton,

View File

@@ -63,7 +63,6 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api'
(update)="onUpdate($event)"
></table>
`,
standalone: true,
imports: [
CommonModule,
TuiNotification,

View File

@@ -2,7 +2,6 @@ import { Pipe, PipeTransform } from '@angular/core'
@Pipe({
name: 'duration',
standalone: true,
})
export class DurationPipe implements PipeTransform {
transform(start: string, finish: string): number {

View File

@@ -3,7 +3,6 @@ import { BackupTargetType } from 'src/app/services/api/api.types'
@Pipe({
name: 'getBackupIcon',
standalone: true,
})
export class GetBackupIconPipe implements PipeTransform {
transform(type: BackupTargetType = 'disk') {

View File

@@ -5,7 +5,6 @@ import { GetBackupIconPipe } from './get-backup-icon.pipe'
@Pipe({
name: 'getDisplayInfo',
standalone: true,
})
export class GetDisplayInfoPipe implements PipeTransform {
readonly icon = new GetBackupIconPipe()

View File

@@ -3,7 +3,6 @@ import { BackupReport } from 'src/app/services/api/api.types'
@Pipe({
name: 'hasError',
standalone: true,
})
export class HasErrorPipe implements PipeTransform {
transform(report: BackupReport): boolean {

View File

@@ -3,7 +3,6 @@ import cronstrue from 'cronstrue'
@Pipe({
name: 'toHumanCron',
standalone: true,
})
export class ToHumanCronPipe implements PipeTransform {
transform(cron: string): { message: string; color: string } {

View File

@@ -8,7 +8,6 @@ import { Version } from '@start9labs/start-sdk'
@Pipe({
name: 'toOptions',
standalone: true,
})
export class ToOptionsPipe implements PipeTransform {
private readonly config = inject(ConfigService)

View File

@@ -1,6 +1,6 @@
import { inject, Injectable } from '@angular/core'
import { Router } from '@angular/router'
import * as argon2 from '@start9labs/argon2'
import { verify } from '@start9labs/argon2'
import {
ErrorService,
LoadingService,
@@ -79,7 +79,7 @@ export class BackupsRestoreService {
hash: string | null,
): Observable<RecoverData> {
return of(password).pipe(
tap(() => argon2.verify(hash || '', password)),
tap(() => verify(hash || '', password)),
switchMap(() => {
const loader = this.loader.open('Decrypting drive').subscribe()

View File

@@ -11,7 +11,6 @@ import { TuiHeader } from '@taiga-ui/layout'
import { TitleDirective } from 'src/app/services/title.service'
@Component({
standalone: true,
selector: 'logs-header',
template: `
<ng-container *title>

View File

@@ -0,0 +1,199 @@
import { KeyValuePipe } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
inject,
signal,
} from '@angular/core'
import { i18nKey, i18nPipe } from '@start9labs/shared'
import { TuiAppearance, TuiButton, TuiIcon, TuiTitle } from '@taiga-ui/core'
import { TuiCardMedium } from '@taiga-ui/layout'
import { LogsComponent } from 'src/app/routes/portal/components/logs/logs.component'
import { RR } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { TitleDirective } from 'src/app/services/title.service'
interface Log {
title: i18nKey
subtitle: i18nKey
icon: string
follow: (params: RR.FollowServerLogsReq) => Promise<RR.FollowServerLogsRes>
fetch: (params: RR.GetServerLogsReq) => Promise<RR.GetServerLogsRes>
}
@Component({
template: `
<ng-container *title>
@if (current(); as key) {
<button
tuiIconButton
iconStart="@tui.arrow-left"
(click)="current.set(null)"
>
{{ 'Back' | i18n }}
</button>
{{ logs[key]?.title | i18n }}
} @else {
{{ 'Logs' | i18n }}
}
</ng-container>
@if (current(); as key) {
<header tuiTitle>
<strong class="title">
<button
tuiIconButton
appearance="secondary-grayscale"
iconStart="@tui.x"
size="s"
class="close"
(click)="current.set(null)"
>
{{ 'Close' | i18n }}
</button>
{{ logs[key]?.title | i18n }}
</strong>
<p tuiSubtitle>{{ logs[key]?.subtitle | i18n }}</p>
</header>
@for (log of logs | keyvalue; track $index) {
@if (log.key === current()) {
<logs
[context]="log.key"
[followLogs]="log.value.follow"
[fetchLogs]="log.value.fetch"
/>
}
}
} @else {
@for (log of logs | keyvalue; track $index) {
<button
tuiCardMedium
tuiAppearance="neutral"
(click)="current.set(log.key)"
>
<tui-icon [icon]="log.value.icon" />
<span tuiTitle>
<strong>{{ log.value.title | i18n }}</strong>
<span tuiSubtitle>{{ log.value.subtitle | i18n }}</span>
</span>
<tui-icon icon="@tui.chevron-right" />
</button>
}
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'g-page' },
styles: `
:host {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 1rem;
padding: 1rem;
}
header {
width: 100%;
padding: 0 1rem;
}
strong {
font-weight: 700;
}
logs {
height: calc(100% - 4rem);
width: 100%;
}
.close {
position: absolute;
right: 0;
border-radius: 100%;
}
button::before {
margin: 0 -0.25rem 0 -0.375rem;
--tui-icon-size: 1.5rem;
}
[tuiCardMedium] {
height: 14rem;
width: 14rem;
cursor: pointer;
box-shadow:
inset 0 0 0 1px var(--tui-background-neutral-1),
var(--tui-shadow-small);
[tuiSubtitle] {
color: var(--tui-text-secondary);
}
tui-icon:last-child {
align-self: flex-end;
}
}
:host-context(tui-root._mobile) {
flex-direction: column;
justify-content: flex-start;
header {
padding: 0;
}
.title {
display: none;
}
logs {
height: calc(100% - 2rem);
}
[tuiCardMedium] {
width: 100%;
height: auto;
gap: 1rem;
}
}
`,
imports: [
LogsComponent,
TitleDirective,
KeyValuePipe,
TuiTitle,
TuiCardMedium,
TuiIcon,
TuiAppearance,
TuiButton,
i18nPipe,
],
})
export default class SystemLogsComponent {
private readonly api = inject(ApiService)
readonly current = signal<string | null>(null)
readonly logs: Record<string, Log> = {
os: {
title: 'OS Logs',
subtitle: 'Raw, unfiltered operating system logs',
icon: '@tui.square-dashed-bottom-code',
follow: params => this.api.followServerLogs(params),
fetch: params => this.api.getServerLogs(params),
},
kernel: {
title: 'Kernel Logs',
subtitle: 'Diagnostics for drivers and other kernel processes',
icon: '@tui.square-chevron-right',
follow: params => this.api.followKernelLogs(params),
fetch: params => this.api.getKernelLogs(params),
},
tor: {
title: 'Tor Logs',
subtitle: 'Diagnostic logs for the Tor daemon on StartOS',
icon: '@tui.globe',
follow: params => this.api.followTorLogs(params),
fetch: params => this.api.getTorLogs(params),
},
}
}

View File

@@ -6,7 +6,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { LogsHeaderComponent } from '../components/header.component'
@Component({
standalone: true,
template: `
<logs-header [title]="'Kernel Logs' | i18n">
{{ 'Diagnostics for drivers and other kernel processes' | i18n }}

View File

@@ -6,7 +6,6 @@ import { RR } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
standalone: true,
template: `
<logs-header [title]="'OS Logs' | i18n">
{{ 'Raw, unfiltered operating system logs' | i18n }}

View File

@@ -19,7 +19,6 @@ import { TitleDirective } from 'src/app/services/title.service'
</a>
}
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'g-page' },
styles: [

View File

@@ -6,7 +6,6 @@ import { RR } from 'src/app/services/api/api.types'
import { ApiService } from 'src/app/services/api/embassy-api.service'
@Component({
standalone: true,
template: `
<logs-header [title]="'Tor Logs' | i18n">
{{ 'Diagnostic logs for the Tor daemon on StartOS' | i18n }}

View File

@@ -92,7 +92,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
</button>
}
`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,

View File

@@ -7,7 +7,6 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
import { DialogService, i18nPipe } from '@start9labs/shared'
@Component({
standalone: true,
selector: 'marketplace-menu',
template: `
<menu [registry]="registry$ | async">
@@ -27,20 +26,18 @@ import { DialogService, i18nPipe } from '@start9labs/shared'
</button>
</menu>
`,
styles: [
`
.mobile-button {
display: flex;
gap: 0.5rem;
padding: 1.25rem;
font-size: 1rem;
line-height: 1.5rem;
background-color: transparent;
background-image: none;
border: none;
}
`,
],
styles: `
.mobile-button {
display: flex;
gap: 0.5rem;
padding: 1.25rem;
font-size: 1rem;
line-height: 1.5rem;
background-color: transparent;
background-image: none;
border: none;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,

View File

@@ -3,7 +3,6 @@ import { i18nPipe, knownRegistries, sameUrl } from '@start9labs/shared'
import { TuiNotification } from '@taiga-ui/core'
@Component({
standalone: true,
selector: 'marketplace-notification',
template: `
<tui-notification
@@ -45,14 +44,12 @@ import { TuiNotification } from '@taiga-ui/core'
}
</tui-notification>
`,
styles: [
`
.notification-wrapper {
margin: 1rem;
pointer-events: none;
}
`,
],
styles: `
.notification-wrapper {
margin: 1rem;
pointer-events: none;
}
`,
imports: [TuiNotification, i18nPipe],
})
export class MarketplaceNotificationComponent {

View File

@@ -64,59 +64,56 @@ import { MarketplaceControlsComponent } from './controls.component'
</tui-drawer>
</marketplace-item>
`,
styles: [
`
:host {
cursor: pointer;
animation: animateIn 400ms calc(var(--animation-order) * 200ms) both;
styles: `
:host {
cursor: pointer;
animation: animateIn 400ms calc(var(--animation-order) * 200ms) both;
}
tui-drawer {
top: 0;
width: 28rem;
border-radius: 0;
}
@keyframes animateIn {
from {
opacity: 0;
transform: scale(0.6) translateY(-20px);
}
tui-drawer {
top: 0;
width: 28rem;
border-radius: 0;
to {
opacity: 1;
}
}
@keyframes animateIn {
from {
opacity: 0;
transform: scale(0.6) translateY(-20px);
}
.preview-wrapper {
overflow-y: auto;
height: 100%;
max-width: 100%;
to {
opacity: 1;
}
@media (min-width: 768px) {
max-width: 30rem;
}
}
.preview-wrapper {
overflow-y: auto;
height: 100%;
max-width: 100%;
.close-button {
place-self: end;
margin-bottom: 0;
@media (min-width: 768px) {
max-width: 30rem;
}
@media (min-width: 768px) {
margin-bottom: 2rem;
}
}
.close-button {
place-self: end;
margin-bottom: 0;
@media (min-width: 768px) {
margin-bottom: 2rem;
}
}
.controls-wrapper {
display: flex;
justify-content: flex-start;
gap: 0.5rem;
height: 4.5rem;
}
`,
],
.controls-wrapper {
display: flex;
justify-content: flex-start;
gap: 0.5rem;
height: 4.5rem;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
providers: [
{
provide: TuiDropdownService,

View File

@@ -20,7 +20,6 @@ import { MarketplaceTileComponent } from './components/tile.component'
import { StorageService } from 'src/app/services/storage.service'
@Component({
standalone: true,
template: `
<ng-container *title>{{ 'Marketplace' | i18n }}</ng-container>
<marketplace-menu />
@@ -56,18 +55,16 @@ import { StorageService } from 'src/app/services/storage.service'
</tui-scrollbar>
`,
host: { class: 'g-page' },
styles: [
`
:host {
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0;
background: rgb(55 58 63 / 90%)
url('/assets/img/background_marketplace.jpg') no-repeat top right;
background-size: cover;
&::before {
styles: `
:host {
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0;
background: rgb(55 58 63 / 90%)
url('/assets/img/background_marketplace.jpg') no-repeat top right;
background-size: cover;
&::before {
content: '';
position: absolute;
inset: 0;
@@ -75,81 +72,80 @@ import { StorageService } from 'src/app/services/storage.service'
}
}
.marketplace-content {
&-wrapper {
@media (min-width: 768px) {
padding-left: 17rem;
}
@media (min-width: 1536px) {
padding-left: 18rem;
}
.marketplace-content {
&-wrapper {
@media (min-width: 768px) {
padding-left: 17rem;
}
&-inner {
padding-top: 6rem;
@media (min-width: 1536px) {
padding-left: 18rem;
}
}
@media (min-width: 768px) {
padding: 0 2rem 2.5rem 2rem;
}
&-inner {
padding-top: 6rem;
.title-wrapper {
margin: 2rem 0 2.5rem 0;
padding: 0 1.5rem;
@media (min-width: 768px) {
padding: 0 2rem 2.5rem 2rem;
}
h1 {
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: 700;
color: rgb(250 250 250 / 0.8);
pointer-events: none;
.title-wrapper {
margin: 2rem 0 2.5rem 0;
padding: 0 1.5rem;
@media (min-width: 640px) {
font-size: 3rem;
line-height: 1;
}
h1 {
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: 700;
color: rgb(250 250 250 / 0.8);
pointer-events: none;
@media (min-width: 640px) {
font-size: 3rem;
line-height: 1;
}
}
}
}
&-list {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 4rem 3rem;
padding: 1.5rem;
&-list {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 4rem 3rem;
padding: 1.5rem;
@media (min-width: 768px) {
padding: 2rem;
}
@media (min-width: 1024px) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 1280px) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (min-width: 1536px) {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
@media (min-width: 768px) {
padding: 2rem;
}
@media (min-width: 1024px) {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 1280px) {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (min-width: 1536px) {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.tile-wrapper {
display: block;
height: 100%;
}
.tile-wrapper {
display: block;
height: 100%;
}
}
}
.loading-text {
font-size: 1.25rem;
line-height: 1.75rem;
padding-left: 1.5rem;
font-weight: normal;
}
.loading-text {
font-size: 1.25rem;
line-height: 1.75rem;
padding-left: 1.5rem;
font-weight: normal;
}
:host-context(tui-root._mobile) {
padding: 0;
}
`,
],
:host-context(tui-root._mobile) {
padding: 0;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterPackagesPipe],
imports: [

View File

@@ -33,7 +33,6 @@ import {
map,
startWith,
switchMap,
tap,
} from 'rxjs'
import { MarketplaceService } from 'src/app/services/marketplace.service'
@@ -94,58 +93,55 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
}
</div>
`,
styles: [
`
:host {
pointer-events: auto;
styles: `
:host {
pointer-events: auto;
}
.outer-container {
display: flex;
flex-direction: column;
height: calc(100vh - var(--portal-header-height) - var(--bumper));
}
.inner-container {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
column-gap: 2rem;
}
.listing {
font-size: 0.8rem;
// @TODO theme
color: #8059e5;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.3rem;
tui-icon {
width: 0.8em;
height: 0.8em;
}
}
.outer-container {
display: flex;
flex-direction: column;
height: calc(100vh - var(--portal-header-height) - var(--bumper));
}
.versions {
border: 0;
border-top-width: 1px;
border-bottom-width: 1px;
border-color: rgb(113 113 122);
border-style: solid;
cursor: pointer;
.inner-container {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
column-gap: 2rem;
}
.listing {
font-size: 0.8rem;
// @TODO theme
color: #8059e5;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.3rem;
tui-icon {
width: 0.8em;
height: 0.8em;
}
}
.versions {
border: 0;
border-top-width: 1px;
border-bottom-width: 1px;
border-color: rgb(113 113 122);
border-style: solid;
::ng-deep label {
cursor: pointer;
::ng-deep label {
cursor: pointer;
}
}
}
marketplace-additional {
padding-bottom: 2rem;
}
`,
],
standalone: true,
marketplace-additional {
padding-bottom: 2rem;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,

View File

@@ -28,20 +28,20 @@ import { IST, utils } from '@start9labs/start-sdk'
import { StorageService } from 'src/app/services/storage.service'
@Component({
standalone: true,
template: `
@if (registries$ | async; as registries) {
<h3 class="g-title">{{ 'Default Registries' | i18n }}</h3>
@for (registry of registries.standard; track $index) {
<button
tuiCell
class="g-stretch"
[disabled]="registry.selected"
[registry]="registry"
(click)="connect(registry.url)"
></button>
}
<h3 class="g-title">{{ 'Custom Registries' | i18n }}</h3>
<button tuiCell (click)="add()">
<button tuiCell class="g-stretch" (click)="add()">
<tui-icon icon="@tui.plus" [style.margin-inline.rem]="'0.5'" />
<div tuiTitle>{{ 'Add custom registry' | i18n }}</div>
</button>
@@ -49,6 +49,7 @@ import { StorageService } from 'src/app/services/storage.service'
<div class="connect-container">
<button
tuiCell
class="g-stretch"
[registry]="registry"
(click)="connect(registry.url)"
></button>
@@ -64,19 +65,13 @@ import { StorageService } from 'src/app/services/storage.service'
}
}
`,
styles: [
`
.connect-container {
display: flex;
flex-direction: row;
align-items: center;
}
[tuiCell] {
width: stretch;
}
`,
],
styles: `
.connect-container {
display: flex;
flex-direction: row;
align-items: center;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,

View File

@@ -17,7 +17,6 @@ const LABELS: Record<string, i18nKey> = {
}
@Component({
standalone: true,
selector: 'metrics-cpu',
template: `
<div class="cpu">
@@ -28,7 +27,7 @@ const LABELS: Record<string, i18nKey> = {
<metrics-data [labels]="labels" [value]="value()" />
`,
styles: `
@import '@taiga-ui/core/styles/taiga-ui-local';
@use '@taiga-ui/core/styles/taiga-ui-local' as taiga;
.cpu {
position: relative;
@@ -58,7 +57,7 @@ const LABELS: Record<string, i18nKey> = {
}
.arrow {
@include transition(transform);
@include taiga.transition(transform);
position: absolute;
top: 50%;
left: 50%;

View File

@@ -11,7 +11,6 @@ import { ValuePipe } from './value.pipe'
import { i18nKey, i18nPipe } from '@start9labs/shared'
@Component({
standalone: true,
selector: 'metrics-data',
template: `
@for (key of keys(); track $index) {

View File

@@ -21,7 +21,6 @@ const LABELS: Record<string, i18nKey> = {
}
@Component({
standalone: true,
selector: 'metrics-memory',
template: `
<label tuiProgressLabel>

View File

@@ -16,7 +16,6 @@ import { UptimeComponent } from './uptime.component'
import { i18nPipe } from '@start9labs/shared'
@Component({
standalone: true,
selector: 'app-metrics',
template: `
<ng-container *title>{{ 'Metrics' | i18n }}</ng-container>

View File

@@ -17,7 +17,6 @@ const LABELS: Record<string, i18nKey> = {
}
@Component({
standalone: true,
selector: 'metrics-storage',
template: `
<progress

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