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