mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
chore: comments (#2863)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { Exver, MarkdownPipeModule } from '@start9labs/shared'
|
||||
import { Exver, MarkdownPipe } from '@start9labs/shared'
|
||||
import { TuiButton, TuiDialogContext, TuiLoader } from '@taiga-ui/core'
|
||||
import { TuiAccordion } from '@taiga-ui/kit'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
@@ -21,13 +21,7 @@ import { MarketplacePkg } from '../../src/types'
|
||||
</tui-accordion>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
CommonModule,
|
||||
TuiButton,
|
||||
TuiLoader,
|
||||
TuiAccordion,
|
||||
MarkdownPipeModule,
|
||||
],
|
||||
imports: [CommonModule, TuiButton, TuiLoader, TuiAccordion, MarkdownPipe],
|
||||
})
|
||||
export class ReleaseNotesComponent {
|
||||
private readonly exver = inject(Exver)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { NgModule } from '@angular/core'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { AboutComponent } from './about.component'
|
||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||
import { MarkdownPipeModule, SafeLinksDirective } from '@start9labs/shared'
|
||||
import { MarkdownPipe, SafeLinksDirective } from '@start9labs/shared'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -14,7 +14,7 @@ import { MarkdownPipeModule, SafeLinksDirective } from '@start9labs/shared'
|
||||
TuiTagModule,
|
||||
NgDompurifyModule,
|
||||
SafeLinksDirective,
|
||||
MarkdownPipeModule,
|
||||
MarkdownPipe,
|
||||
TuiButton,
|
||||
],
|
||||
declarations: [AboutComponent],
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import {
|
||||
getErrorMessage,
|
||||
MarkdownPipeModule,
|
||||
SafeLinksDirective,
|
||||
} from '@start9labs/shared'
|
||||
import { TuiLoader, TuiNotification } from '@taiga-ui/core'
|
||||
import { ActivatedRoute, Data } from '@angular/router'
|
||||
import { TuiDialogContext, TuiLoader, TuiNotification } from '@taiga-ui/core'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||
import { catchError, ignoreElements, of } from 'rxjs'
|
||||
import { SafeLinksDirective } from '../directives/safe-links.directive'
|
||||
import { MarkdownPipe } from '../pipes/markdown.pipe'
|
||||
import { getErrorMessage } from '../services/error.service'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -21,7 +20,7 @@ import { catchError, ignoreElements, of } from 'rxjs'
|
||||
@if (content(); as result) {
|
||||
<div safeLinks [innerHTML]="result | markdown | dompurify"></div>
|
||||
} @else {
|
||||
<tui-loader textContent="Loading" />
|
||||
<tui-loader textContent="Loading" [style.height.%]="100" />
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -30,13 +29,15 @@ import { catchError, ignoreElements, of } from 'rxjs'
|
||||
imports: [
|
||||
TuiNotification,
|
||||
TuiLoader,
|
||||
MarkdownPipeModule,
|
||||
NgDompurifyModule,
|
||||
MarkdownPipe,
|
||||
SafeLinksDirective,
|
||||
],
|
||||
})
|
||||
export default class ServiceMarkdownRoute {
|
||||
private readonly data = inject(ActivatedRoute).snapshot.data
|
||||
export class MarkdownComponent {
|
||||
private readonly data =
|
||||
injectContext<TuiDialogContext<void, Data>>({ optional: true })?.data ||
|
||||
inject(ActivatedRoute).snapshot.data
|
||||
|
||||
readonly content = toSignal<string>(this.data['content'])
|
||||
readonly error = toSignal(
|
||||
@@ -46,3 +47,5 @@ export default class ServiceMarkdownRoute {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export const MARKDOWN = new PolymorpheusComponent(MarkdownComponent)
|
||||
@@ -1,18 +0,0 @@
|
||||
<tui-notification
|
||||
*ngIf="error$ | async as error"
|
||||
appearance="negative"
|
||||
safeLinks
|
||||
>
|
||||
{{ error }}
|
||||
</tui-notification>
|
||||
|
||||
<div
|
||||
*ngIf="content$ | async as result; else loading"
|
||||
safeLinks
|
||||
class="content-padding"
|
||||
[innerHTML]="result | markdown | dompurify"
|
||||
></div>
|
||||
|
||||
<ng-template #loading>
|
||||
<tui-loader [textContent]="'Loading ' + title | titlecase" />
|
||||
</ng-template>
|
||||
@@ -1,22 +0,0 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { TuiLoader, TuiNotification } from '@taiga-ui/core'
|
||||
import { NgDompurifyModule } from '@tinkoff/ng-dompurify'
|
||||
import { SafeLinksDirective } from '../../directives/safe-links.directive'
|
||||
|
||||
import { MarkdownPipeModule } from '../../pipes/markdown/markdown.module'
|
||||
import { MarkdownComponent } from './markdown.component'
|
||||
|
||||
@NgModule({
|
||||
declarations: [MarkdownComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MarkdownPipeModule,
|
||||
SafeLinksDirective,
|
||||
NgDompurifyModule,
|
||||
TuiLoader,
|
||||
TuiNotification,
|
||||
],
|
||||
exports: [MarkdownComponent],
|
||||
})
|
||||
export class MarkdownModule {}
|
||||
@@ -1,18 +0,0 @@
|
||||
.content-padding {
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
|
||||
:host ::ng-deep img {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep h1,
|
||||
:host ::ng-deep h2,
|
||||
:host ::ng-deep h3,
|
||||
:host ::ng-deep h4,
|
||||
:host ::ng-deep h5,
|
||||
:host ::ng-deep h6,
|
||||
:host ::ng-deep hr,
|
||||
:host ::ng-deep p {
|
||||
margin: revert;
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Component, Inject } from '@angular/core'
|
||||
import { TuiDialogContext } from '@taiga-ui/core'
|
||||
import {
|
||||
POLYMORPHEUS_CONTEXT,
|
||||
PolymorpheusComponent,
|
||||
} from '@taiga-ui/polymorpheus'
|
||||
import {
|
||||
catchError,
|
||||
ignoreElements,
|
||||
share,
|
||||
defer,
|
||||
isObservable,
|
||||
Observable,
|
||||
of,
|
||||
} from 'rxjs'
|
||||
|
||||
import { getErrorMessage } from '../../services/error.service'
|
||||
|
||||
@Component({
|
||||
selector: 'markdown',
|
||||
templateUrl: './markdown.component.html',
|
||||
styleUrls: ['./markdown.component.scss'],
|
||||
})
|
||||
export class MarkdownComponent {
|
||||
readonly content$ = defer(() =>
|
||||
isObservable(this.context.data.content)
|
||||
? this.context.data.content
|
||||
: of(this.context.data.content),
|
||||
).pipe(share())
|
||||
|
||||
readonly error$ = this.content$.pipe(
|
||||
ignoreElements(),
|
||||
catchError(e => of(getErrorMessage(e))),
|
||||
)
|
||||
|
||||
constructor(
|
||||
@Inject(POLYMORPHEUS_CONTEXT)
|
||||
private readonly context: TuiDialogContext<
|
||||
void,
|
||||
{ content: string | Observable<string> }
|
||||
>,
|
||||
) {}
|
||||
|
||||
get title(): string {
|
||||
return this.context.label || ''
|
||||
}
|
||||
}
|
||||
|
||||
export const MARKDOWN = new PolymorpheusComponent(MarkdownComponent)
|
||||
@@ -2,6 +2,7 @@ import { Pipe, PipeTransform } from '@angular/core'
|
||||
import { marked } from 'marked'
|
||||
|
||||
@Pipe({
|
||||
standalone: true,
|
||||
name: 'markdown',
|
||||
})
|
||||
export class MarkdownPipe implements PipeTransform {
|
||||
@@ -1,8 +0,0 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { MarkdownPipe } from './markdown.pipe'
|
||||
|
||||
@NgModule({
|
||||
declarations: [MarkdownPipe],
|
||||
exports: [MarkdownPipe],
|
||||
})
|
||||
export class MarkdownPipeModule {}
|
||||
@@ -10,11 +10,10 @@ export * from './components/initializing/initializing.component'
|
||||
export * from './components/loading/loading.component'
|
||||
export * from './components/loading/loading.component'
|
||||
export * from './components/loading/loading.service'
|
||||
export * from './components/markdown/markdown.component'
|
||||
export * from './components/markdown/markdown.component.module'
|
||||
export * from './components/ticker/ticker.component'
|
||||
export * from './components/ticker/ticker.module'
|
||||
export * from './components/drive.component'
|
||||
export * from './components/markdown.component'
|
||||
export * from './components/server.component'
|
||||
|
||||
export * from './directives/drag-scroller.directive'
|
||||
@@ -22,14 +21,13 @@ export * from './directives/safe-links.directive'
|
||||
|
||||
export * from './pipes/exver/exver.module'
|
||||
export * from './pipes/exver/exver.pipe'
|
||||
export * from './pipes/markdown/markdown.module'
|
||||
export * from './pipes/markdown/markdown.pipe'
|
||||
export * from './pipes/shared/shared.module'
|
||||
export * from './pipes/shared/empty.pipe'
|
||||
export * from './pipes/shared/includes.pipe'
|
||||
export * from './pipes/shared/trust.pipe'
|
||||
export * from './pipes/unit-conversion/unit-conversion.module'
|
||||
export * from './pipes/unit-conversion/unit-conversion.pipe'
|
||||
export * from './pipes/markdown.pipe'
|
||||
|
||||
export * from './services/copy.service'
|
||||
export * from './services/download-html.service'
|
||||
|
||||
@@ -1,55 +1,47 @@
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { TuiDialogContext, TuiIcon } from '@taiga-ui/core'
|
||||
import {
|
||||
POLYMORPHEUS_CONTEXT,
|
||||
PolymorpheusComponent,
|
||||
} from '@taiga-ui/polymorpheus'
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { BackupReport } from 'src/app/services/api/api.types'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<h3 class="g-title">Completed: {{ timestamp | date: 'medium' }}</h3>
|
||||
<div class="g-action">
|
||||
<div [style.flex]="1">
|
||||
<h3 class="g-title">Completed: {{ data.createdAt | date: 'medium' }}</h3>
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>System data</strong>
|
||||
<div [style.color]="system.color">{{ system.result }}</div>
|
||||
<div tuiSubtitle [style.color]="system.color">{{ system.result }}</div>
|
||||
</div>
|
||||
<tui-icon [icon]="system.icon" [style.color]="system.color" />
|
||||
</div>
|
||||
<div *ngFor="let pkg of report?.packages | keyvalue" class="g-action">
|
||||
<div [style.flex]="1">
|
||||
<strong>{{ pkg.key }}</strong>
|
||||
<div [style.color]="getColor(pkg.value.error)">
|
||||
{{ pkg.value.error ? 'Failed: ' + pkg.value.error : 'Succeeded' }}
|
||||
@for (pkg of data.content.packages | keyvalue; track $index) {
|
||||
<div tuiCell>
|
||||
<div tuiTitle>
|
||||
<strong>{{ pkg.key }}</strong>
|
||||
<div tuiSubtitle [style.color]="getColor(pkg.value.error)">
|
||||
{{ pkg.value.error ? 'Failed: ' + pkg.value.error : 'Succeeded' }}
|
||||
</div>
|
||||
</div>
|
||||
<tui-icon
|
||||
[icon]="getIcon(pkg.value.error)"
|
||||
[style.color]="getColor(pkg.value.error)"
|
||||
/>
|
||||
</div>
|
||||
<tui-icon
|
||||
[icon]="getIcon(pkg.value.error)"
|
||||
[style.color]="getColor(pkg.value.error)"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, TuiIcon],
|
||||
imports: [CommonModule, TuiIcon, TuiCell, TuiTitle],
|
||||
})
|
||||
export class BackupsReportModal {
|
||||
private readonly context =
|
||||
inject<
|
||||
TuiDialogContext<void, { content: BackupReport; timestamp: string }>
|
||||
>(POLYMORPHEUS_CONTEXT)
|
||||
readonly data =
|
||||
injectContext<
|
||||
TuiDialogContext<void, { content: BackupReport; createdAt: string }>
|
||||
>().data
|
||||
|
||||
readonly system = this.getSystem()
|
||||
|
||||
get report(): BackupReport {
|
||||
return this.context.data.content
|
||||
}
|
||||
|
||||
get timestamp(): string {
|
||||
return this.context.data.timestamp
|
||||
}
|
||||
|
||||
getColor(error: unknown) {
|
||||
return error ? 'var(--tui-text-negative)' : 'var(--tui-text-positive)'
|
||||
}
|
||||
@@ -59,7 +51,7 @@ export class BackupsReportModal {
|
||||
}
|
||||
|
||||
private getSystem() {
|
||||
if (!this.report.server.attempted) {
|
||||
if (!this.data.content.server.attempted) {
|
||||
return {
|
||||
result: 'Not Attempted',
|
||||
icon: '@tui.minus',
|
||||
@@ -67,9 +59,9 @@ export class BackupsReportModal {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.report.server.error) {
|
||||
if (this.data.content.server.error) {
|
||||
return {
|
||||
result: `Failed: ${this.report.server.error}`,
|
||||
result: `Failed: ${this.data.content.server.error}`,
|
||||
icon: '@tui.circle-minus',
|
||||
color: 'var(--tui-text-negative)',
|
||||
}
|
||||
|
||||
@@ -44,11 +44,6 @@ export default {
|
||||
check: 'Check for updates',
|
||||
},
|
||||
},
|
||||
sync: {
|
||||
title: 'Clock sync failure',
|
||||
subtitle:
|
||||
'This will cause connectivity issues. To resolve it, refer to the',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -46,11 +46,6 @@ export default {
|
||||
check: 'Buscar actualizaciones',
|
||||
},
|
||||
},
|
||||
sync: {
|
||||
title: 'Fallo en la sincronización del reloj',
|
||||
subtitle:
|
||||
'Esto causará problemas de conectividad. Para resolverlo, consulta la',
|
||||
},
|
||||
},
|
||||
},
|
||||
} satisfies i18n
|
||||
|
||||
@@ -6,13 +6,9 @@ import {
|
||||
viewChild,
|
||||
ViewContainerRef,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { TitleService } from 'src/app/services/title.service'
|
||||
import { HeaderMenuComponent } from './menu.component'
|
||||
import { HeaderNavigationComponent } from './navigation.component'
|
||||
import { HeaderSnekDirective } from './snek.directive'
|
||||
import { HeaderStatusComponent } from './status.component'
|
||||
|
||||
@Component({
|
||||
@@ -21,12 +17,6 @@ import { HeaderStatusComponent } from './status.component'
|
||||
<header-navigation />
|
||||
<div class="item item_center">
|
||||
<div class="mobile"><ng-container #vcr /></div>
|
||||
<img
|
||||
[appSnek]="snekScore()"
|
||||
class="snek"
|
||||
alt="Play Snake"
|
||||
src="assets/img/icons/snek.png"
|
||||
/>
|
||||
</div>
|
||||
<header-status class="item item_connection" />
|
||||
<header-menu class="item item_corner" />
|
||||
@@ -105,19 +95,6 @@ import { HeaderStatusComponent } from './status.component'
|
||||
}
|
||||
}
|
||||
|
||||
.snek {
|
||||
@include center-top();
|
||||
@include transition(opacity);
|
||||
right: 2rem;
|
||||
width: 1rem;
|
||||
opacity: 0.2;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
.item_center::before {
|
||||
left: -2rem;
|
||||
@@ -144,7 +121,6 @@ import { HeaderStatusComponent } from './status.component'
|
||||
imports: [
|
||||
HeaderStatusComponent,
|
||||
HeaderNavigationComponent,
|
||||
HeaderSnekDirective,
|
||||
HeaderMenuComponent,
|
||||
],
|
||||
})
|
||||
@@ -152,15 +128,6 @@ export class HeaderComponent implements OnInit {
|
||||
private readonly title = inject(TitleService)
|
||||
|
||||
readonly vcr = viewChild.required('vcr', { read: ViewContainerRef })
|
||||
readonly snekScore = toSignal(
|
||||
inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||
'ui',
|
||||
'gaming',
|
||||
'snake',
|
||||
'highScore',
|
||||
),
|
||||
{ initialValue: 0 },
|
||||
)
|
||||
|
||||
ngOnInit() {
|
||||
this.title.register(this.vcr())
|
||||
|
||||
@@ -63,7 +63,7 @@ type ClearnetForm = {
|
||||
</ng-template>
|
||||
<button
|
||||
tuiButton
|
||||
appearance="accent"
|
||||
[appearance]="isPublic() ? 'primary-destructive' : 'accent'"
|
||||
[iconStart]="isPublic() ? '@tui.globe-lock' : '@tui.globe'"
|
||||
[style.margin-inline-start]="'auto'"
|
||||
(click)="toggle()"
|
||||
|
||||
@@ -6,8 +6,20 @@ import { tuiInjectElement } from '@taiga-ui/cdk'
|
||||
selector: '[appUptime]',
|
||||
template: '',
|
||||
styles: `
|
||||
:host {
|
||||
&::before {
|
||||
content: 'Uptime: ';
|
||||
}
|
||||
|
||||
&:empty::after {
|
||||
content: '-';
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
display: none;
|
||||
grid-row: 2;
|
||||
margin: 0;
|
||||
font: var(--tui-font-text-ui-s);
|
||||
}
|
||||
`,
|
||||
})
|
||||
@@ -22,7 +34,7 @@ export class UptimeComponent implements OnChanges, OnDestroy {
|
||||
clearInterval(this.interval)
|
||||
|
||||
if (!this.appUptime) {
|
||||
this.el.textContent = '-'
|
||||
this.el.textContent = ''
|
||||
} else {
|
||||
this.el.textContent = uptime(new Date(this.appUptime))
|
||||
this.interval = setInterval(() => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { TuiDialogService, TuiIcon } from '@taiga-ui/core'
|
||||
import { TuiDialogService, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { BackupsUpcomingComponent } from './components/upcoming.component'
|
||||
import { HISTORY } from './modals/history.component'
|
||||
import { JOBS } from './modals/jobs.component'
|
||||
@@ -12,12 +13,12 @@ import { BackupsRestoreService } from './services/restore.service'
|
||||
<section>
|
||||
<h3 class="g-title">Options</h3>
|
||||
@for (option of options; track $index) {
|
||||
<button class="g-action" (click)="option.action()">
|
||||
<button tuiCell (click)="option.action()">
|
||||
<tui-icon [icon]="option.icon" />
|
||||
<div>
|
||||
<span tuiTitle>
|
||||
<strong>{{ option.name }}</strong>
|
||||
<div>{{ option.description }}</div>
|
||||
</div>
|
||||
<span tuiSubtitle>{{ option.description }}</span>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
</section>
|
||||
@@ -27,7 +28,7 @@ import { BackupsRestoreService } from './services/restore.service'
|
||||
host: { class: 'g-page' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [BackupsUpcomingComponent, TuiIcon],
|
||||
imports: [BackupsUpcomingComponent, TuiIcon, TuiCell, TuiTitle],
|
||||
})
|
||||
export default class BackupsComponent {
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
|
||||
@@ -31,10 +31,8 @@ interface Package {
|
||||
@if (pkgs) {
|
||||
@for (pkg of pkgs; track $index) {
|
||||
<label tuiBlock>
|
||||
<div class="g-action">
|
||||
<img class="icon" alt="" [src]="pkg.icon" />
|
||||
{{ pkg.title }}
|
||||
</div>
|
||||
<img class="icon" alt="" [src]="pkg.icon" />
|
||||
{{ pkg.title }}
|
||||
<input
|
||||
type="checkbox"
|
||||
tuiCheckbox
|
||||
|
||||
@@ -228,7 +228,7 @@ export class BackupsHistoryModal {
|
||||
label: 'Backup Report',
|
||||
data: {
|
||||
content: report,
|
||||
timestamp: completedAt,
|
||||
createdAt: completedAt,
|
||||
},
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
@@ -14,7 +14,9 @@ import {
|
||||
TuiDialogService,
|
||||
TuiIcon,
|
||||
TuiLoader,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { BackupTarget } from 'src/app/services/api/api.types'
|
||||
@@ -34,25 +36,22 @@ import { TARGETS } from './targets.component'
|
||||
<h3 class="g-title">Saved Targets</h3>
|
||||
@for (target of targets | keyvalue; track $index) {
|
||||
<button
|
||||
class="g-action"
|
||||
tuiCell
|
||||
[disabled]="isDisabled(target.value)"
|
||||
(click)="select(target.value, target.key)"
|
||||
>
|
||||
@if (target.value | getDisplayInfo; as displayInfo) {
|
||||
<tui-icon [icon]="displayInfo.icon" />
|
||||
<div>
|
||||
<span tuiTitle>
|
||||
<strong>{{ displayInfo.name }}</strong>
|
||||
<backups-status
|
||||
[type]="context.data.type"
|
||||
[mountable]="target.value.mountable"
|
||||
[hasBackup]="hasBackup(target.value)"
|
||||
/>
|
||||
<div [style.color]="'var(--tui-text-secondary'">
|
||||
{{ displayInfo.description }}
|
||||
<br />
|
||||
{{ displayInfo.path }}
|
||||
</div>
|
||||
</div>
|
||||
<span tuiSubtitle>{{ displayInfo.description }}</span>
|
||||
<span tuiSubtitle>{{ displayInfo.path }}</span>
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
} @empty {
|
||||
@@ -70,6 +69,8 @@ import { TARGETS } from './targets.component'
|
||||
BackupsStatusComponent,
|
||||
GetDisplayInfoPipe,
|
||||
KeyValuePipe,
|
||||
TuiCell,
|
||||
TuiTitle,
|
||||
],
|
||||
})
|
||||
export class BackupsTargetModal {
|
||||
|
||||
@@ -32,7 +32,7 @@ const LABELS = {
|
||||
.cpu {
|
||||
position: relative;
|
||||
margin: 1rem auto;
|
||||
width: 7rem;
|
||||
width: 8rem;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ const LABELS = {
|
||||
selector: 'metrics-memory',
|
||||
template: `
|
||||
<label tuiProgressLabel>
|
||||
<tui-progress-circle size="l" [max]="100" [value]="used()" />
|
||||
<tui-progress-circle size="xl" [max]="100" [value]="used()" />
|
||||
{{ value()?.percentageUsed?.value | value }}%
|
||||
</label>
|
||||
<metrics-data [labels]="labels" [value]="value()" />
|
||||
|
||||
@@ -30,7 +30,7 @@ const LABELS = {
|
||||
progress {
|
||||
height: 1.5rem;
|
||||
width: 80%;
|
||||
margin: 3.75rem auto;
|
||||
margin: 4.25rem auto;
|
||||
border-radius: 0;
|
||||
clip-path: none;
|
||||
mask: linear-gradient(to right, #000 80%, transparent 80%);
|
||||
|
||||
@@ -56,6 +56,7 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
|
||||
:host {
|
||||
height: 100%;
|
||||
min-height: 7.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { TuiNotification, TuiTitle } from '@taiga-ui/core'
|
||||
import {
|
||||
TuiHint,
|
||||
TuiIcon,
|
||||
TuiLink,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { TimeService } from 'src/app/services/time.service'
|
||||
|
||||
@Component({
|
||||
@@ -11,18 +18,45 @@ import { TimeService } from 'src/app/services/time.service'
|
||||
@if (now(); as time) {
|
||||
@if (!time.synced) {
|
||||
<tui-notification appearance="warning">
|
||||
NTP not synced, time could be wrong
|
||||
<ng-container *ngTemplateOutlet="hint" />
|
||||
</tui-notification>
|
||||
}
|
||||
<div tuiTitle>
|
||||
<div tuiSubtitle class="g-secondary">
|
||||
{{ time.now | date: 'h:mm a z' : 'UTC' }}
|
||||
<div tuiCell>
|
||||
<div tuiTitle [style.text-align]="'center'">
|
||||
<div tuiSubtitle class="g-secondary">
|
||||
{{ time.now | date: 'h:mm a z' : 'UTC' }}
|
||||
</div>
|
||||
<b>{{ time.now | date: 'MMMM d, y' : 'UTC' }}</b>
|
||||
</div>
|
||||
<b>{{ time.now | date: 'MMMM d, y' : 'UTC' }}</b>
|
||||
@if (!time.synced) {
|
||||
<tui-icon
|
||||
icon="@tui.circle-alert"
|
||||
class="g-warning"
|
||||
[tuiHint]="hint"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
Loading...
|
||||
}
|
||||
<ng-template #hint>
|
||||
<div tuiTitle>
|
||||
Clock sync failure
|
||||
<div tuiSubtitle>
|
||||
To resolve it, refer to
|
||||
<a
|
||||
tuiLink
|
||||
iconEnd="@tui.external-link"
|
||||
appearance=""
|
||||
href="https://docs.start9.com/0.3.5.x/support/common-issues#clock-sync-failure"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
[pseudo]="true"
|
||||
[textContent]="'the docs'"
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
`,
|
||||
styles: `
|
||||
:host {
|
||||
@@ -32,14 +66,39 @@ import { TimeService } from 'src/app/services/time.service'
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
[tuiCell],
|
||||
[tuiTitle],
|
||||
[tuiSubtitle] {
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
[tuiTitle] {
|
||||
text-align: center;
|
||||
tui-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
tui-notification {
|
||||
display: none;
|
||||
}
|
||||
|
||||
tui-icon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiNotification, DatePipe, TuiTitle],
|
||||
imports: [
|
||||
CommonModule,
|
||||
TuiNotification,
|
||||
TuiTitle,
|
||||
TuiLink,
|
||||
TuiCell,
|
||||
TuiIcon,
|
||||
TuiHint,
|
||||
],
|
||||
})
|
||||
export class TimeComponent {
|
||||
readonly now = toSignal(inject(TimeService).now$)
|
||||
|
||||
@@ -19,7 +19,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
@if (['running', 'starting', 'restarting'].includes(status)) {
|
||||
<button
|
||||
tuiButton
|
||||
appearance="outline-destructive"
|
||||
appearance="secondary-destructive"
|
||||
iconStart="@tui.square"
|
||||
(click)="actions.stop(manifest)"
|
||||
>
|
||||
@@ -30,7 +30,6 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
@if (status === 'running') {
|
||||
<button
|
||||
tuiButton
|
||||
appearance="outline"
|
||||
iconStart="@tui.rotate-cw"
|
||||
(click)="actions.restart(manifest)"
|
||||
>
|
||||
@@ -41,7 +40,6 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
@if (status === 'stopped') {
|
||||
<button
|
||||
tuiButton
|
||||
appearance="outline"
|
||||
iconStart="@tui.play"
|
||||
(click)="actions.start(manifest, hasUnmet(dependencies))"
|
||||
>
|
||||
@@ -52,19 +50,21 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
inline-size: 20rem;
|
||||
max-inline-size: 100%;
|
||||
margin-block-start: 1rem;
|
||||
|
||||
&:nth-child(3) {
|
||||
grid-row: span 2;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
inline-size: min-content;
|
||||
|
||||
[tuiButton] {
|
||||
font-size: 0;
|
||||
|
||||
@@ -5,13 +5,10 @@ import {
|
||||
Input,
|
||||
} from '@angular/core'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||
import { TuiButton, TuiLink } from '@taiga-ui/core'
|
||||
import { TuiBadge } from '@taiga-ui/kit'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||
import { getManifest } from '../../../../../utils/get-package-data'
|
||||
import { MappedInterface } from '../types/mapped-interface'
|
||||
|
||||
@Component({
|
||||
@@ -30,25 +27,21 @@ import { MappedInterface } from '../types/mapped-interface'
|
||||
</td>
|
||||
<td>
|
||||
@if (info.public) {
|
||||
<button
|
||||
tuiButton
|
||||
size="s"
|
||||
<a
|
||||
class="hosting"
|
||||
tuiLink
|
||||
iconStart="@tui.globe"
|
||||
appearance="positive"
|
||||
(click)="toggle()"
|
||||
>
|
||||
Public
|
||||
</button>
|
||||
[textContent]="'Public'"
|
||||
></a>
|
||||
} @else {
|
||||
<button
|
||||
tuiButton
|
||||
size="s"
|
||||
<a
|
||||
class="hosting"
|
||||
tuiLink
|
||||
iconStart="@tui.lock"
|
||||
appearance="negative"
|
||||
(click)="toggle()"
|
||||
>
|
||||
Private
|
||||
</button>
|
||||
[textContent]="'Private'"
|
||||
></a>
|
||||
}
|
||||
</td>
|
||||
<td [style.grid-area]="'span 2'">
|
||||
@@ -70,6 +63,24 @@ import { MappedInterface } from '../types/mapped-interface'
|
||||
</td>
|
||||
`,
|
||||
styles: `
|
||||
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||
|
||||
:host {
|
||||
cursor: pointer;
|
||||
clip-path: inset(0 round var(--tui-radius-m));
|
||||
@include transition(background);
|
||||
}
|
||||
|
||||
[tuiLink] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media ($tui-mouse) {
|
||||
:host:hover {
|
||||
background: var(--tui-background-neutral-1);
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -88,6 +99,10 @@ import { MappedInterface } from '../types/mapped-interface'
|
||||
td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hosting {
|
||||
font-size: 0;
|
||||
}
|
||||
}
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
@@ -96,9 +111,6 @@ import { MappedInterface } from '../types/mapped-interface'
|
||||
})
|
||||
export class ServiceInterfaceComponent {
|
||||
private readonly config = inject(ConfigService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
@Input({ required: true })
|
||||
info!: MappedInterface
|
||||
@@ -125,31 +137,4 @@ export class ServiceInterfaceComponent {
|
||||
? 'null'
|
||||
: this.config.launchableAddress(this.info, this.pkg.hosts)
|
||||
}
|
||||
|
||||
async toggle() {
|
||||
const loader = this.loader
|
||||
.open(`Making ${this.info.public ? 'private' : 'public'}`)
|
||||
.subscribe()
|
||||
|
||||
const params = {
|
||||
internalPort: this.info.addressInfo.internalPort,
|
||||
public: !this.info.public,
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.info.public) {
|
||||
await this.api.pkgBindingSetPubic({
|
||||
...params,
|
||||
host: this.info.addressInfo.hostId,
|
||||
package: getManifest(this.pkg).id,
|
||||
})
|
||||
} else {
|
||||
await this.api.serverBindingSetPubic(params)
|
||||
}
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
loader.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
inject,
|
||||
input,
|
||||
} from '@angular/core'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { TuiTable } from '@taiga-ui/addon-table'
|
||||
import { tuiDefaultSort } from '@taiga-ui/cdk'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
@@ -31,6 +32,7 @@ import { ServiceInterfaceComponent } from './interface.component'
|
||||
@for (info of interfaces(); track $index) {
|
||||
<tr
|
||||
serviceInterface
|
||||
[routerLink]="info.routerLink"
|
||||
[info]="info"
|
||||
[pkg]="pkg()"
|
||||
[disabled]="disabled()"
|
||||
@@ -46,7 +48,7 @@ import { ServiceInterfaceComponent } from './interface.component'
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ServiceInterfaceComponent, TuiTable],
|
||||
imports: [ServiceInterfaceComponent, TuiTable, RouterLink],
|
||||
})
|
||||
export class ServiceInterfacesComponent {
|
||||
private readonly config = inject(ConfigService)
|
||||
|
||||
@@ -70,8 +70,8 @@ import { InstallingProgressDisplayPipe } from '../pipes/install-progress.pipe'
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
div {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,12 +27,16 @@ import { StatusComponent } from './status.component'
|
||||
<a [routerLink]="routerLink">{{ manifest.title }}</a>
|
||||
</td>
|
||||
<td [style.grid-area]="'2 / 2'">{{ manifest.version }}</td>
|
||||
<td [appUptime]="$any(pkg.status).started"></td>
|
||||
<td
|
||||
[style.grid-area]="'3 / 2'"
|
||||
[appUptime]="$any(pkg.status).started"
|
||||
[style.grid-column]="2"
|
||||
[style.grid-row]="4"
|
||||
></td>
|
||||
<td
|
||||
appStatus
|
||||
[pkg]="pkg"
|
||||
[hasDepErrors]="hasError(depErrors)"
|
||||
[style.grid-area]="'3 / 2'"
|
||||
></td>
|
||||
<td [style.grid-area]="'2 / 3'" [style.text-align]="'center'">
|
||||
<fieldset
|
||||
@@ -56,6 +60,10 @@ import { StatusComponent } from './status.component'
|
||||
}
|
||||
}
|
||||
|
||||
td::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
height: 2rem;
|
||||
@@ -75,7 +83,7 @@ import { StatusComponent } from './status.component'
|
||||
:host-context(tui-root._mobile) {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template: 2rem 2rem 2rem/6rem 1fr 2rem;
|
||||
grid-template: 1.25rem 1.75rem 1.5rem 1.25rem/6rem 1fr 2rem;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
|
||||
@@ -90,6 +98,15 @@ import { StatusComponent } from './status.component'
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--tui-text-secondary);
|
||||
|
||||
&::before {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
INJECTOR,
|
||||
} from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import { CopyService, getPkgId } from '@start9labs/shared'
|
||||
import { CopyService, getPkgId, MarkdownComponent } from '@start9labs/shared'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
FALLBACK_URL,
|
||||
ServiceAdditionalItemComponent,
|
||||
} from '../components/additional-item.component'
|
||||
import ServiceMarkdownRoute from './markdown.component'
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
@@ -52,7 +51,7 @@ import ServiceMarkdownRoute from './markdown.component'
|
||||
export default class ServiceAboutRoute {
|
||||
private readonly copyService = inject(CopyService)
|
||||
private readonly markdown = inject(TuiDialogService).open(
|
||||
new PolymorpheusComponent(ServiceMarkdownRoute, inject(INJECTOR)),
|
||||
new PolymorpheusComponent(MarkdownComponent, inject(INJECTOR)),
|
||||
{ label: 'License', size: 'l' },
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { NgTemplateOutlet } from '@angular/common'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
@@ -10,7 +11,7 @@ import { RouterLink } from '@angular/router'
|
||||
import { getPkgId } from '@start9labs/shared'
|
||||
import { TuiItem } from '@taiga-ui/cdk'
|
||||
import { TuiButton, TuiLink } from '@taiga-ui/core'
|
||||
import { TuiBreadcrumbs } from '@taiga-ui/kit'
|
||||
import { TuiBadge, TuiBreadcrumbs } from '@taiga-ui/kit'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { InterfaceComponent } from 'src/app/routes/portal/components/interfaces/interface.component'
|
||||
import { getAddresses } from 'src/app/routes/portal/components/interfaces/interface.utils'
|
||||
@@ -23,12 +24,16 @@ import { TitleDirective } from 'src/app/services/title.service'
|
||||
<ng-container *title>
|
||||
<a routerLink="../.." tuiIconButton iconStart="@tui.arrow-left">Back</a>
|
||||
{{ interface()?.name }}
|
||||
<ng-container *ngTemplateOutlet="badge" />
|
||||
</ng-container>
|
||||
<tui-breadcrumbs size="l" [style.margin-block-end.rem]="1">
|
||||
<a *tuiItem tuiLink appearance="action-grayscale" routerLink="../..">
|
||||
Dashboard
|
||||
</a>
|
||||
<span *tuiItem class="g-primary">{{ interface()?.name }}</span>
|
||||
<span *tuiItem class="g-primary">
|
||||
{{ interface()?.name }}
|
||||
<ng-container *ngTemplateOutlet="badge" />
|
||||
</span>
|
||||
</tui-breadcrumbs>
|
||||
@if (interface(); as serviceInterface) {
|
||||
<app-interface
|
||||
@@ -36,6 +41,16 @@ import { TitleDirective } from 'src/app/services/title.service'
|
||||
[serviceInterface]="serviceInterface"
|
||||
/>
|
||||
}
|
||||
<ng-template #badge>
|
||||
<tui-badge
|
||||
[iconStart]="interface()?.public ? '@tui.globe' : '@tui.lock'"
|
||||
[style.vertical-align.rem]="-0.125"
|
||||
[style.margin]="'0 0.25rem -0.25rem'"
|
||||
[appearance]="interface()?.public ? 'positive' : 'negative'"
|
||||
>
|
||||
{{ interface()?.public ? 'Public' : 'Private' }}
|
||||
</tui-badge>
|
||||
</ng-template>
|
||||
`,
|
||||
styles: `
|
||||
:host-context(tui-root._mobile) tui-breadcrumbs {
|
||||
@@ -53,6 +68,8 @@ import { TitleDirective } from 'src/app/services/title.service'
|
||||
TuiBreadcrumbs,
|
||||
TuiItem,
|
||||
TuiLink,
|
||||
TuiBadge,
|
||||
NgTemplateOutlet,
|
||||
],
|
||||
})
|
||||
export default class ServiceInterfaceRoute {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { inject } from '@angular/core'
|
||||
import { ActivatedRouteSnapshot, ResolveFn, Routes } from '@angular/router'
|
||||
import { MarkdownComponent } from '@start9labs/shared'
|
||||
import { defer, map, Observable, of } from 'rxjs'
|
||||
import { share } from 'rxjs/operators'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
@@ -22,7 +23,7 @@ export const ROUTES: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'instructions',
|
||||
loadComponent: () => import('./routes/markdown.component'),
|
||||
component: MarkdownComponent,
|
||||
resolve: { content: getStatic('instructions.md') },
|
||||
canActivate: [
|
||||
({ paramMap }: ActivatedRouteSnapshot) => {
|
||||
|
||||
@@ -38,7 +38,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
import { EOSService } from 'src/app/services/eos.service'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { SystemSyncComponent } from './sync.component'
|
||||
import { SnekDirective } from './snek.directive'
|
||||
import { UPDATE } from './update.component'
|
||||
import { SystemWipeComponent } from './wipe.component'
|
||||
|
||||
@@ -61,9 +61,6 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
</hgroup>
|
||||
</header>
|
||||
@if (server(); as server) {
|
||||
@if (!server.ntpSynced) {
|
||||
<system-sync />
|
||||
}
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.zap" />
|
||||
<span tuiTitle>
|
||||
@@ -75,6 +72,7 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
<button
|
||||
tuiButton
|
||||
appearance="accent"
|
||||
iconStart="@tui.refresh-cw"
|
||||
[disabled]="eos.updatingOrBackingUp$ | async"
|
||||
(click)="onUpdate()"
|
||||
>
|
||||
@@ -125,7 +123,7 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
</button>
|
||||
</div>
|
||||
<div tuiCell tuiAppearance="outline-grayscale">
|
||||
<tui-icon icon="@tui.download" />
|
||||
<tui-icon icon="@tui.award" />
|
||||
<span tuiTitle>
|
||||
<strong>
|
||||
{{ 'system.general.ca.title' | i18n }}
|
||||
@@ -134,7 +132,7 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
{{ 'system.general.ca.subtitle' | i18n }}
|
||||
</span>
|
||||
</span>
|
||||
<button tuiButton (click)="downloadCA()">
|
||||
<button tuiButton iconStart="@tui.download" (click)="downloadCA()">
|
||||
{{ 'system.general.ca.button' | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -168,6 +166,12 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<img
|
||||
[snek]="score()"
|
||||
class="snek"
|
||||
alt="Play Snake"
|
||||
src="assets/img/icons/snek.png"
|
||||
/>
|
||||
}
|
||||
<!-- hidden element for downloading cert -->
|
||||
<a id="download-ca" href="/static/local-root-ca.crt"></a>
|
||||
@@ -177,6 +181,16 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
max-inline-size: 40rem;
|
||||
}
|
||||
|
||||
.snek {
|
||||
width: 1rem;
|
||||
opacity: 0.2;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
@@ -205,12 +219,12 @@ import { SystemWipeComponent } from './wipe.component'
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TitleDirective,
|
||||
SystemSyncComponent,
|
||||
TuiButtonLoading,
|
||||
TuiButtonSelect,
|
||||
TuiDataListWrapper,
|
||||
TuiTextfield,
|
||||
FormsModule,
|
||||
SnekDirective,
|
||||
],
|
||||
})
|
||||
export default class SystemGeneralComponent {
|
||||
@@ -235,6 +249,10 @@ export default class SystemGeneralComponent {
|
||||
readonly eos = inject(EOSService)
|
||||
readonly i18n = inject(i18nService)
|
||||
readonly languages = ['english', 'spanish']
|
||||
readonly score = toSignal(
|
||||
this.patch.watch$('ui', 'gaming', 'snake', 'highScore'),
|
||||
{ initialValue: 0 },
|
||||
)
|
||||
|
||||
onUpdate() {
|
||||
if (this.server()?.statusInfo.updated) {
|
||||
|
||||
@@ -42,7 +42,7 @@ import { injectContext } from '@taiga-ui/polymorpheus'
|
||||
],
|
||||
imports: [TuiButton],
|
||||
})
|
||||
export class HeaderSnekComponent implements AfterViewInit, OnDestroy {
|
||||
export class SnekComponent implements AfterViewInit, OnDestroy {
|
||||
private readonly document = inject(DOCUMENT)
|
||||
private readonly dialog = injectContext<TuiDialogContext<number, number>>()
|
||||
|
||||
@@ -4,31 +4,31 @@ import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { filter } from 'rxjs'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { HeaderSnekComponent } from './snek.component'
|
||||
import { SnekComponent } from './snek.component'
|
||||
|
||||
@Directive({
|
||||
standalone: true,
|
||||
selector: 'img[appSnek]',
|
||||
selector: 'img[snek]',
|
||||
})
|
||||
export class HeaderSnekDirective {
|
||||
export class SnekDirective {
|
||||
private readonly dialogs = inject(TuiDialogService)
|
||||
private readonly loader = inject(LoadingService)
|
||||
private readonly errorService = inject(ErrorService)
|
||||
private readonly api = inject(ApiService)
|
||||
|
||||
@Input()
|
||||
appSnek = 0
|
||||
snek = 0
|
||||
|
||||
@HostListener('click')
|
||||
async onClick() {
|
||||
this.dialogs
|
||||
.open<number>(new PolymorpheusComponent(HeaderSnekComponent), {
|
||||
.open<number>(new PolymorpheusComponent(SnekComponent), {
|
||||
label: 'Snake!',
|
||||
closeable: false,
|
||||
dismissible: false,
|
||||
data: this.appSnek,
|
||||
data: this.snek,
|
||||
})
|
||||
.pipe(filter(score => score > this.appSnek))
|
||||
.pipe(filter(score => score > this.snek))
|
||||
.subscribe(async score => {
|
||||
const loader = this.loader.open('Saving high score...').subscribe()
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||
import { TuiLink, TuiNotification, TuiTitle } from '@taiga-ui/core'
|
||||
import { i18nPipe } from 'src/app/i18n/i18n.pipe'
|
||||
|
||||
@Component({
|
||||
selector: 'system-sync',
|
||||
template: `
|
||||
<tui-notification appearance="warning">
|
||||
<div tuiTitle>
|
||||
{{ 'system.general.sync.title' | i18n }}
|
||||
<div tuiSubtitle>
|
||||
{{ 'system.general.sync.subtitle' | i18n }}
|
||||
<a
|
||||
tuiLink
|
||||
iconEnd="@tui.external-link"
|
||||
href="https://docs.start9.com/0.3.5.x/support/common-issues#clock-sync-failure"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
[textContent]="'StartOS docs'"
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</tui-notification>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [TuiNotification, TuiTitle, TuiLink, i18nPipe],
|
||||
})
|
||||
export class SystemSyncComponent {}
|
||||
@@ -4,7 +4,7 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import {
|
||||
ErrorService,
|
||||
LoadingService,
|
||||
MarkdownPipeModule,
|
||||
MarkdownPipe,
|
||||
SafeLinksDirective,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
@@ -36,7 +36,7 @@ import { EOSService } from 'src/app/services/eos.service'
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
MarkdownPipeModule,
|
||||
MarkdownPipe,
|
||||
NgDompurifyModule,
|
||||
SafeLinksDirective,
|
||||
TuiAutoFocus,
|
||||
|
||||
@@ -39,7 +39,7 @@ import { SessionsTableComponent } from './table.component'
|
||||
<button
|
||||
tuiButton
|
||||
size="xs"
|
||||
appearance="negative"
|
||||
appearance="primary-destructive"
|
||||
[style.margin-inline-start]="'auto'"
|
||||
[disabled]="!selected.length"
|
||||
(click)="terminate(selected, others || [])"
|
||||
@@ -60,7 +60,6 @@ import { SessionsTableComponent } from './table.component'
|
||||
TuiLet,
|
||||
RouterLink,
|
||||
TitleDirective,
|
||||
TuiTable,
|
||||
TuiHeader,
|
||||
TuiTitle,
|
||||
],
|
||||
|
||||
@@ -87,15 +87,19 @@ import { PlatformInfoPipe } from './platform-info.pipe'
|
||||
grid-template-columns: 2.5rem 1fr;
|
||||
|
||||
&:has(:checked) .platform {
|
||||
color: var(--tui-text-action);
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
@include fullsize();
|
||||
z-index: 1;
|
||||
opacity: 0;
|
||||
transform: none;
|
||||
left: 0.25rem;
|
||||
|
||||
&:not(:checked) {
|
||||
@include fullsize();
|
||||
z-index: 1;
|
||||
visibility: hidden;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
|
||||
@@ -8,19 +8,14 @@ import {
|
||||
} from '@angular/core'
|
||||
import { RouterLink } from '@angular/router'
|
||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||
import { MarkdownPipeModule, SafeLinksDirective } from '@start9labs/shared'
|
||||
import { MarkdownPipe, SafeLinksDirective } from '@start9labs/shared'
|
||||
import { TuiResponsiveDialogService } from '@taiga-ui/addon-mobile'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiIcon,
|
||||
TuiLink,
|
||||
TuiLoader,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiButton, TuiIcon, TuiLink, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiExpand } from '@taiga-ui/experimental'
|
||||
import {
|
||||
TUI_CONFIRM,
|
||||
TuiAvatar,
|
||||
TuiButtonLoading,
|
||||
TuiChevron,
|
||||
TuiFade,
|
||||
TuiProgressCircle,
|
||||
@@ -84,20 +79,22 @@ import UpdatesComponent from './updates.component'
|
||||
"
|
||||
/>
|
||||
} @else {
|
||||
@if (ready()) {
|
||||
<button
|
||||
tuiButton
|
||||
iconStart="@tui.arrow-big-up-dash"
|
||||
[appearance]="error() ? 'destructive' : 'primary'"
|
||||
(click.stop)="onClick()"
|
||||
>
|
||||
{{ error() ? 'Retry' : 'Update' }}
|
||||
</button>
|
||||
} @else {
|
||||
<tui-loader [style.width.rem]="2" [inheritColor]="true" />
|
||||
}
|
||||
<button
|
||||
tuiButton
|
||||
size="s"
|
||||
[loading]="!ready()"
|
||||
[appearance]="error() ? 'destructive' : 'primary'"
|
||||
(click.stop)="onClick()"
|
||||
>
|
||||
{{ error() ? 'Retry' : 'Update' }}
|
||||
</button>
|
||||
}
|
||||
<button tuiIconButton appearance="icon" [tuiChevron]="expanded()">
|
||||
<button
|
||||
tuiIconButton
|
||||
size="s"
|
||||
appearance="icon"
|
||||
[tuiChevron]="expanded()"
|
||||
>
|
||||
Show more
|
||||
</button>
|
||||
</div>
|
||||
@@ -119,15 +116,16 @@ import UpdatesComponent from './updates.component'
|
||||
</p>
|
||||
<p tuiTitle>
|
||||
<span>
|
||||
<b>What's new</b>
|
||||
(
|
||||
<a
|
||||
tuiLink
|
||||
iconEnd="@tui.external-link"
|
||||
routerLink="/portal/marketplace"
|
||||
[queryParams]="{ url: parent.current()?.url, id: item().id }"
|
||||
>
|
||||
View listing
|
||||
</a>
|
||||
<b>What's new</b>
|
||||
[textContent]="'View listing'"
|
||||
></a>
|
||||
)
|
||||
</span>
|
||||
</p>
|
||||
<p
|
||||
@@ -139,8 +137,6 @@ import UpdatesComponent from './updates.component'
|
||||
</tr>
|
||||
`,
|
||||
styles: `
|
||||
@import '@taiga-ui/core/styles/taiga-ui-local';
|
||||
|
||||
:host {
|
||||
display: contents;
|
||||
}
|
||||
@@ -161,13 +157,6 @@ import UpdatesComponent from './updates.component'
|
||||
word-break: break-word;
|
||||
clip-path: inset(0 round var(--tui-radius-s));
|
||||
cursor: pointer;
|
||||
@include transition(background);
|
||||
|
||||
@media ($tui-mouse) {
|
||||
&:hover {
|
||||
background: var(--tui-background-neutral-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
@@ -190,10 +179,6 @@ import UpdatesComponent from './updates.component'
|
||||
&[colspan]:only-child {
|
||||
padding: 0 3rem;
|
||||
text-align: left;
|
||||
|
||||
[tuiLink] {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,11 +202,6 @@ import UpdatesComponent from './updates.component'
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
[tuiButton] {
|
||||
font-size: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
@@ -236,19 +216,19 @@ import UpdatesComponent from './updates.component'
|
||||
RouterLink,
|
||||
TuiExpand,
|
||||
TuiButton,
|
||||
TuiButtonLoading,
|
||||
TuiChevron,
|
||||
TuiAvatar,
|
||||
TuiLink,
|
||||
TuiIcon,
|
||||
TuiLoader,
|
||||
TuiProgressCircle,
|
||||
TuiTitle,
|
||||
MarkdownPipeModule,
|
||||
TuiFade,
|
||||
MarkdownPipe,
|
||||
NgDompurifyModule,
|
||||
SafeLinksDirective,
|
||||
DatePipe,
|
||||
InstallingProgressPipe,
|
||||
TuiFade,
|
||||
],
|
||||
})
|
||||
export class UpdatesItemComponent {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, map, tap } from 'rxjs'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import {
|
||||
@@ -91,43 +92,35 @@ interface UpdatesData {
|
||||
Request Failed
|
||||
</tui-notification>
|
||||
}
|
||||
<section class="g-card" [style.padding]="'0 1rem 1rem'">
|
||||
<table tuiTable class="g-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th tuiTh>Name</th>
|
||||
<th tuiTh>Version</th>
|
||||
<th tuiTh>Package Hash</th>
|
||||
<th tuiTh>Published</th>
|
||||
<th tuiTh></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if (
|
||||
data()?.marketplace?.[current()?.url || '']?.packages;
|
||||
as packages
|
||||
) {
|
||||
@if (packages | filterUpdates: data()?.localPkgs; as updates) {
|
||||
@for (pkg of updates; track $index) {
|
||||
<updates-item
|
||||
[item]="pkg"
|
||||
[local]="data()?.localPkgs?.[pkg.id]!"
|
||||
/>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="5">All services are up to date!</td>
|
||||
</tr>
|
||||
}
|
||||
<section class="g-card">
|
||||
<header>{{ current()?.name }}</header>
|
||||
<table
|
||||
[appTable]="['Name', 'Version', 'Package Hash', 'Published', '']"
|
||||
>
|
||||
@if (
|
||||
data()?.marketplace?.[current()?.url || '']?.packages;
|
||||
as packages
|
||||
) {
|
||||
@if (packages | filterUpdates: data()?.localPkgs; as updates) {
|
||||
@for (pkg of updates; track $index) {
|
||||
<updates-item
|
||||
[item]="pkg"
|
||||
[local]="data()?.localPkgs?.[pkg.id]!"
|
||||
/>
|
||||
} @empty {
|
||||
<tr>
|
||||
<td colspan="5">All services are up to date!</td>
|
||||
</tr>
|
||||
}
|
||||
} @else {
|
||||
<tr>
|
||||
<td colspan="5" [tuiSkeleton]="true">Loading</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5" [tuiSkeleton]="true">Loading</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
} @else {
|
||||
<tr>
|
||||
<td colspan="5" [tuiSkeleton]="true">Loading</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="5" [tuiSkeleton]="true">Loading</td>
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
</section>
|
||||
</section>
|
||||
@@ -172,6 +165,11 @@ interface UpdatesData {
|
||||
section {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[tuiCell] {
|
||||
@@ -203,7 +201,6 @@ interface UpdatesData {
|
||||
TuiTitle,
|
||||
TuiNotification,
|
||||
TuiSkeleton,
|
||||
TuiTable,
|
||||
TuiBadgeNotification,
|
||||
TuiFade,
|
||||
TuiButton,
|
||||
@@ -211,6 +208,7 @@ interface UpdatesData {
|
||||
FilterUpdatesPipe,
|
||||
UpdatesItemComponent,
|
||||
TitleDirective,
|
||||
TableComponent,
|
||||
],
|
||||
})
|
||||
export default class UpdatesComponent {
|
||||
|
||||
@@ -27,10 +27,12 @@ export class BadgeService {
|
||||
private readonly notifications = inject(NotificationService)
|
||||
private readonly exver = inject(Exver)
|
||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||
private readonly system$ = combineLatest([
|
||||
this.patch.watch$('serverInfo', 'ntpSynced'),
|
||||
inject(EOSService).updateAvailable$,
|
||||
]).pipe(map(([synced, update]) => Number(!synced) + Number(update)))
|
||||
private readonly system$ = inject(EOSService).updateAvailable$.pipe(
|
||||
map(Number),
|
||||
)
|
||||
private readonly metrics$ = this.patch
|
||||
.watch$('serverInfo', 'ntpSynced')
|
||||
.pipe(map(synced => Number(!synced)))
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
|
||||
private readonly local$ = inject(ConnectionService).pipe(
|
||||
@@ -86,6 +88,8 @@ export class BadgeService {
|
||||
return this.updates$
|
||||
case '/portal/system':
|
||||
return this.system$
|
||||
case '/portal/metrics':
|
||||
return this.metrics$
|
||||
case '/portal/notifications':
|
||||
return this.notifications.unreadCount$
|
||||
default:
|
||||
|
||||
@@ -2,7 +2,7 @@ import { inject, Injectable } from '@angular/core'
|
||||
import { ErrorService, MARKDOWN } from '@start9labs/shared'
|
||||
import { TuiDialogService } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { firstValueFrom, merge, shareReplay, Subject } from 'rxjs'
|
||||
import { firstValueFrom, merge, of, shareReplay, Subject } from 'rxjs'
|
||||
import { REPORT } from 'src/app/components/report.component'
|
||||
import {
|
||||
ServerNotification,
|
||||
@@ -94,13 +94,14 @@ export class NotificationService {
|
||||
full = false,
|
||||
) {
|
||||
const label = full || code === 2 ? title : 'Backup Report'
|
||||
const content = code === 1 ? REPORT : MARKDOWN
|
||||
const component = code === 1 ? REPORT : MARKDOWN
|
||||
const content = code === 1 ? data : of(data)
|
||||
|
||||
this.dialogs
|
||||
.open(full ? message : content, {
|
||||
.open(full ? message : component, {
|
||||
label,
|
||||
data: {
|
||||
content: data,
|
||||
content,
|
||||
timestamp: createdAt,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -147,11 +147,6 @@ hr {
|
||||
|
||||
> table[tuiTable] {
|
||||
margin: 0 -0.5rem;
|
||||
|
||||
td:empty,
|
||||
th:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
@@ -183,6 +178,7 @@ hr {
|
||||
border-radius: var(--tui-radius-s);
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 0 1px var(--tui-background-neutral-1);
|
||||
clip-path: inset(0 round var(--tui-radius-s));
|
||||
|
||||
td,
|
||||
th {
|
||||
@@ -301,41 +297,6 @@ hr {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.g-action {
|
||||
@include transition(background);
|
||||
@include button-clear();
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: stretch;
|
||||
gap: 1rem;
|
||||
text-align: left;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0 -1rem;
|
||||
line-height: 1.25rem;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--tui-text-primary);
|
||||
}
|
||||
|
||||
a.g-action,
|
||||
button.g-action {
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
opacity: var(--tui-disabled-opacity);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--tui-background-neutral-1);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
box-shadow: 0 calc(0.5rem + 1px) 0 -0.5rem var(--tui-background-neutral-1);
|
||||
}
|
||||
}
|
||||
|
||||
.g-toggle {
|
||||
height: var(--tui-height-l);
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user