mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()" />
|
||||
`,
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { DOCUMENT } from '@angular/common'
|
||||
templateUrl: './login.page.html',
|
||||
styleUrls: ['./login.page.scss'],
|
||||
providers: [],
|
||||
standalone: false,
|
||||
})
|
||||
export class LoginPage {
|
||||
password = ''
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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>[]) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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 + & {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -21,6 +21,7 @@ import { tuiPure } from '@taiga-ui/cdk'
|
||||
useExisting: FormGroupName,
|
||||
},
|
||||
],
|
||||
standalone: false,
|
||||
})
|
||||
export class FormUnionComponent implements OnChanges {
|
||||
@Input({ required: true })
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -18,7 +18,6 @@ import { InterfaceComponent } from './interface.component'
|
||||
import { DOCUMENT } from '@angular/common'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'td[actions]',
|
||||
template: `
|
||||
<div class="desktop">
|
||||
|
||||
@@ -47,7 +47,6 @@ type ClearnetForm = {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'section[clearnet]',
|
||||
template: `
|
||||
<header>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,7 +8,6 @@ import { MaskPipe } from './mask.pipe'
|
||||
import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'section[local]',
|
||||
template: `
|
||||
<header>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiBadge } from '@taiga-ui/kit'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'interface-status',
|
||||
template: `
|
||||
<tui-badge
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { LogsComponent } from './logs.component'
|
||||
|
||||
@Directive({
|
||||
standalone: true,
|
||||
selector: 'button[logsDownload]',
|
||||
})
|
||||
export class LogsDownloadDirective {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -47,4 +47,5 @@
|
||||
|
||||
pre {
|
||||
overflow: visible;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -29,7 +29,6 @@ import { LogsComponent } from './logs.component'
|
||||
|
||||
@Pipe({
|
||||
name: 'logs',
|
||||
standalone: true,
|
||||
})
|
||||
export class LogsPipe implements PipeTransform {
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
@@ -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: `
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -132,7 +132,6 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api'
|
||||
}
|
||||
}
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [
|
||||
TuiNotification,
|
||||
TuiButton,
|
||||
|
||||
@@ -59,7 +59,6 @@ import { RecoverOption } from '../types/recover-option'
|
||||
</ng-container>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
|
||||
@@ -8,7 +8,6 @@ interface Data {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
@for (server of context.data.servers; track $index) {
|
||||
<button
|
||||
|
||||
@@ -61,7 +61,6 @@ import { TARGETS } from './targets.component'
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
TuiLoader,
|
||||
TuiButton,
|
||||
|
||||
@@ -63,7 +63,6 @@ import { DocsLinkDirective } from 'projects/shared/src/public-api'
|
||||
(update)="onUpdate($event)"
|
||||
></table>
|
||||
`,
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TuiNotification,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 } {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -19,7 +19,6 @@ import { TitleDirective } from 'src/app/services/title.service'
|
||||
</a>
|
||||
}
|
||||
`,
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
host: { class: 'g-page' },
|
||||
styles: [
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -92,7 +92,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
</button>
|
||||
}
|
||||
`,
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -21,7 +21,6 @@ const LABELS: Record<string, i18nKey> = {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: 'metrics-memory',
|
||||
template: `
|
||||
<label tuiProgressLabel>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user