chore: address comments

This commit is contained in:
waterplea
2024-05-20 21:59:46 +01:00
parent 85b39ecf99
commit 5d8114b475
18 changed files with 211 additions and 147 deletions

View File

@@ -9,13 +9,10 @@ import {
} from '@taiga-ui/core'
import { TuiButtonModule, TuiIconModule } from '@taiga-ui/experimental'
import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { filter } from 'rxjs'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { AuthService } from 'src/app/services/auth.service'
import { ABOUT } from './about.component'
import { getAllPackages } from 'src/app/utils/get-package-data'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { HeaderConnectionComponent } from './connection.component'
@Component({
@@ -114,7 +111,6 @@ export class HeaderMenuComponent {
private readonly errorService = inject(ErrorService)
private readonly loader = inject(LoadingService)
private readonly auth = inject(AuthService)
private readonly patch = inject(PatchDB<DataModel>)
private readonly dialogs = inject(TuiDialogService)
readonly links = [
@@ -136,10 +132,6 @@ export class HeaderMenuComponent {
]
readonly system = [
{
icon: 'tuiIconTool',
action: 'System Rebuild',
},
{
icon: 'tuiIconRefreshCw',
action: 'Restart',
@@ -159,20 +151,17 @@ export class HeaderMenuComponent {
this.auth.setUnverified()
}
async prompt(action: keyof typeof METHODS) {
const minutes =
action === 'System Rebuild'
? Object.keys(await getAllPackages(this.patch)).length * 2
: ''
async prompt(action: 'Restart' | 'Shutdown') {
this.dialogs
.open(TUI_PROMPT, getOptions(action, minutes))
.open(TUI_PROMPT, getOptions(action))
.pipe(filter(Boolean))
.subscribe(async () => {
const loader = this.loader.open(`Beginning ${action}...`).subscribe()
try {
await this.api[METHODS[action]]({})
await this.api[
action === 'Restart' ? 'restartServer' : 'shutdownServer'
]({})
} catch (e: any) {
this.errorService.handleError(e)
} finally {
@@ -182,19 +171,11 @@ export class HeaderMenuComponent {
}
}
const METHODS = {
Restart: 'restartServer',
Shutdown: 'shutdownServer',
'System Rebuild': 'systemRebuild',
} as const
function getOptions(
key: keyof typeof METHODS,
minutes: unknown,
operation: 'Restart' | 'Shutdown',
): Partial<TuiDialogOptions<TuiPromptData>> {
switch (key) {
case 'Restart':
return {
return operation === 'Restart'
? {
label: 'Restart',
size: 's',
data: {
@@ -204,8 +185,7 @@ function getOptions(
no: 'Cancel',
},
}
case 'Shutdown':
return {
: {
label: 'Warning',
size: 's',
data: {
@@ -215,15 +195,4 @@ function getOptions(
no: 'Cancel',
},
}
default:
return {
label: 'Warning',
size: 's',
data: {
content: `This action will tear down all service containers and rebuild them from scratch. No data will be deleted. This action is useful if your system gets into a bad state, and it should only be performed if you are experiencing general performance or reliability issues. It may take up to ${minutes} minutes to complete. During this time, you will lose all connectivity to your server.`,
yes: 'Rebuild',
no: 'Cancel',
},
}
}
}

View File

@@ -1,5 +1,11 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import {
ChangeDetectionStrategy,
Component,
inject,
Input,
} from '@angular/core'
import { RouterLink } from '@angular/router'
import { WINDOW } from '@ng-web-apis/common'
import { TuiIconModule } from '@taiga-ui/experimental'
import { Breadcrumb } from 'src/app/services/breadcrumbs.service'
@@ -7,8 +13,12 @@ import { Breadcrumb } from 'src/app/services/breadcrumbs.service'
standalone: true,
selector: '[headerMobile]',
template: `
@if (headerMobile?.length) {
<a [routerLink]="back" [style.padding.rem]="0.75">
@if (headerMobile && headerMobile.length > 1) {
<a
[routerLink]="back"
[style.padding.rem]="0.75"
[queryParams]="queryParams"
>
<tui-icon icon="tuiIconArrowLeft" />
</a>
}
@@ -46,6 +56,11 @@ import { Breadcrumb } from 'src/app/services/breadcrumbs.service'
.title {
@include text-overflow();
max-width: calc(100% - 5rem);
text-transform: capitalize;
&:first-child {
margin-inline-start: 1rem;
}
}
`,
],
@@ -53,10 +68,15 @@ import { Breadcrumb } from 'src/app/services/breadcrumbs.service'
imports: [TuiIconModule, RouterLink],
})
export class HeaderMobileComponent {
private readonly win = inject(WINDOW)
@Input() headerMobile: readonly Breadcrumb[] | null = []
get title() {
return this.headerMobile?.[this.headerMobile?.length - 1]?.title || ''
return (
this.headerMobile?.[this.headerMobile?.length - 1]?.title ||
(this.win.location.search ? 'Utilities' : 'Services')
)
}
get back() {
@@ -65,4 +85,8 @@ export class HeaderMobileComponent {
'/portal/dashboard'
)
}
get queryParams() {
return this.back === '/portal/dashboard' ? { tab: 'utilities' } : null
}
}

View File

@@ -24,10 +24,8 @@ import { NotificationService } from 'src/app/services/notification.service'
<a
tuiTabBarItem
icon="tuiIconActivity"
routerLink="/portal/dashboard"
routerLink="/portal/system/metrics"
routerLinkActive
[routerLinkActiveOptions]="{ exact: true }"
[queryParams]="{ tab: 'metrics' }"
>
Metrics
</a>
@@ -58,7 +56,9 @@ import { NotificationService } from 'src/app/services/notification.service'
display: none;
// TODO: Theme
--tui-elevation-01: #333;
--tui-base-01: #fff;
--tui-base-04: var(--tui-clear);
--tui-error-fill: #f52222;
backdrop-filter: blur(1rem);
}

View File

@@ -67,6 +67,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
:host {
padding: 0;
border: none;
cursor: default;
}
:host-context(tui-root._mobile) {

View File

@@ -176,7 +176,8 @@ import { TimeService } from 'src/app/services/time.service'
section {
flex-wrap: wrap;
padding: 1rem;
padding: 0;
margin: 1rem -1rem;
}
aside {

View File

@@ -4,6 +4,7 @@ import {
Component,
inject,
Input,
OnChanges,
} from '@angular/core'
import { RouterLink } from '@angular/router'
import { tuiPure } from '@taiga-ui/cdk'
@@ -19,7 +20,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
selector: 'tr[appService]',
template: `
<td [style.grid-area]="'1 / 1 / 4'">
<a [routerLink]="routerLink"><img alt="logo" [src]="pkg.icon" /></a>
<img alt="logo" [src]="pkg.icon" />
</td>
<td [style.grid-area]="'1 / 2'">
<a [routerLink]="routerLink">{{ manifest.title }}</a>
@@ -36,11 +37,25 @@ import { getManifest } from 'src/app/utils/get-package-data'
appControls
[disabled]="!installed || !(connected$ | async)"
[pkg]="pkg"
(click.stop)="(0)"
></fieldset>
</td>
`,
styles: `
@import '@taiga-ui/core/styles/taiga-ui-local';
:host {
@include transition(background);
clip-path: inset(0 round 0.5rem);
cursor: pointer;
&:hover {
background: var(--tui-clear);
}
}
img {
display: block;
height: 2rem;
width: 2rem;
border-radius: 100%;
@@ -80,10 +95,13 @@ import { getManifest } from 'src/app/utils/get-package-data'
}
}
`,
hostDirectives: [RouterLink],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [RouterLink, AsyncPipe, StatusComponent, ControlsComponent],
})
export class ServiceComponent {
export class ServiceComponent implements OnChanges {
private readonly link = inject(RouterLink)
@Input()
pkg!: PackageDataEntry
@@ -104,6 +122,10 @@ export class ServiceComponent {
return `/portal/service/${this.manifest.id}`
}
ngOnChanges() {
this.link.routerLink = this.routerLink
}
@tuiPure
hasError(errors: PkgDependencyErrors = {}): boolean {
return Object.values(errors).some(Boolean)

View File

@@ -19,9 +19,7 @@ import { DepErrorService } from 'src/app/services/dep-error.service'
<th>Name</th>
<th>Version</th>
<th [style.width.rem]="13">Status</th>
<th [style.width.rem]="8" [style.text-align]="'center'">
Controls
</th>
<th [style.width.rem]="8" [style.text-indent.rem]="1">Controls</th>
</tr>
</thead>
<tbody>

View File

@@ -9,8 +9,9 @@ import { LogsComponent } from '../../../components/logs/logs.component'
@Component({
template: `
<tui-select
tuiTextfieldAppearance="unstyled"
tuiTextfieldAppearance="secondary"
tuiTextfieldSize="m"
[style.max-width.rem]="26"
[(ngModel)]="logs"
>
{{ subtitle }}
@@ -50,7 +51,7 @@ import { LogsComponent } from '../../../components/logs/logs.component'
}
logs {
height: calc(100% - 4rem);
height: calc(100% - 5rem);
}
`,
],

View File

@@ -0,0 +1,17 @@
import { AsyncPipe } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { MetricsComponent } from 'src/app/routes/portal/routes/dashboard/metrics.component'
import { MetricsService } from 'src/app/services/metrics.service'
@Component({
template: `
<app-metrics [metrics]="metrics$ | async" />
`,
host: { class: 'g-page' },
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [MetricsComponent, AsyncPipe],
})
export default class SystemMetricsComponent {
readonly metrics$ = inject(MetricsService)
}

View File

@@ -8,7 +8,7 @@ import {
import { RouterLink } from '@angular/router'
import { T } from '@start9labs/start-sdk'
import { tuiPure } from '@taiga-ui/cdk'
import { TuiSvgModule } from '@taiga-ui/core'
import { TuiLinkModule, TuiSvgModule } from '@taiga-ui/core'
import { TuiLineClampModule } from '@taiga-ui/kit'
import { PatchDB } from 'patch-db-client'
import { first, Observable } from 'rxjs'
@@ -20,10 +20,10 @@ import { toRouterLink } from 'src/app/utils/to-router-link'
@Component({
selector: '[notificationItem]',
template: `
<td><ng-content /></td>
<td [style.padding-top.rem]="0.4"><ng-content /></td>
<td>{{ notificationItem.createdAt | date: 'MMM d, y, h:mm a' }}</td>
<td [style.color]="color">
<tui-svg [style.color]="color" [src]="icon"></tui-svg>
<tui-svg [src]="icon" />
{{ notificationItem.title }}
</td>
<td>
@@ -43,20 +43,44 @@ import { toRouterLink } from 'src/app/utils/to-router-link'
[content]="notificationItem.message"
(overflownChange)="overflow = $event"
/>
<a *ngIf="overflow" (click)="service.viewFull(notificationItem)">
<button
*ngIf="overflow"
tuiLink
(click)="service.viewFull(notificationItem)"
>
View Full
</a>
<a
</button>
<button
*ngIf="notificationItem.code === 1"
tuiLink
(click)="service.viewReport(notificationItem)"
>
View Report
</a>
</button>
</td>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, RouterLink, TuiLineClampModule, TuiSvgModule],
host: {
'[class._new]': '!notificationItem.read',
},
styles: `
:host._new {
background: var(--tui-clear);
}
td {
padding: 0.25rem;
vertical-align: top;
}
`,
imports: [
CommonModule,
RouterLink,
TuiLineClampModule,
TuiSvgModule,
TuiLinkModule,
],
})
export class NotificationItemComponent {
private readonly patch = inject(PatchDB<DataModel>)

View File

@@ -14,25 +14,24 @@ import { NotificationsTableComponent } from './table.component'
template: `
<ng-container *tuiLet="notifications$ | async as notifications">
<h3 class="g-title">
Notifications
<ng-container *ngIf="table.selected$ | async as selected">
<tui-hosted-dropdown
tuiDropdownAlign="right"
[content]="dropdown"
[sided]="true"
[(open)]="open"
<tui-hosted-dropdown
*ngIf="table.selected$ | async as selected"
tuiDropdownAlign="right"
[content]="dropdown"
[sided]="true"
[(open)]="open"
[canOpen]="!!selected.length"
>
<button
appearance="primary"
iconRight="tuiIconChevronDown"
tuiButton
size="xs"
type="button"
[disabled]="!selected.length"
>
<button
appearance="primary"
iconRight="tuiIconChevronDown"
tuiButton
size="xs"
type="button"
[disabled]="!selected.length"
>
Batch Action
</button>
</tui-hosted-dropdown>
Batch Action
</button>
<ng-template #dropdown>
<tui-data-list>
<button tuiOption (click)="markSeen(notifications!, selected)">
@@ -46,7 +45,7 @@ import { NotificationsTableComponent } from './table.component'
</button>
</tui-data-list>
</ng-template>
</ng-container>
</tui-hosted-dropdown>
</h3>
<table #table class="g-table" [notifications]="notifications"></table>
</ng-container>

View File

@@ -1,20 +1,18 @@
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
Input,
OnChanges,
} from '@angular/core'
import { FormsModule } from '@angular/forms'
import { TuiCheckboxModule } from '@taiga-ui/experimental'
import { TuiLineClampModule } from '@taiga-ui/kit'
import { BehaviorSubject } from 'rxjs'
import {
ServerNotification,
ServerNotifications,
} from 'src/app/services/api/api.types'
import { TuiForModule } from '@taiga-ui/cdk'
import { BehaviorSubject } from 'rxjs'
import { TuiLineClampModule } from '@taiga-ui/kit'
import { FormsModule } from '@angular/forms'
import { NotificationItemComponent } from './item.component'
import { TuiCheckboxModule } from '@taiga-ui/experimental'
@Component({
selector: 'table[notifications]',
@@ -39,39 +37,40 @@ import { TuiCheckboxModule } from '@taiga-ui/experimental'
</tr>
</thead>
<tbody>
<tr
*ngFor="let notification of notifications; else: loading; empty: blank"
[notificationItem]="notification"
[style.font-weight]="notification.read ? 'normal' : 'bold'"
>
<input
tuiCheckbox
size="s"
type="checkbox"
[style.display]="'block'"
[ngModel]="selected$.value.includes(notification)"
(ngModelChange)="handleToggle(notification)"
/>
</tr>
<ng-template #blank>
<tr>
<td colspan="5">You have no notifications</td>
</tr>
</ng-template>
<ng-template #loading>
<tr *ngFor="let row of ['', '']">
<td colspan="5"><div class="tui-skeleton">Loading</div></td>
</tr>
</ng-template>
@if (notifications) {
@for (notification of notifications; track $index) {
<tr
[notificationItem]="notification"
[style.font-weight]="notification.read ? 'normal' : 'bold'"
>
<input
tuiCheckbox
size="s"
type="checkbox"
[style.display]="'block'"
[ngModel]="selected$.value.includes(notification)"
(ngModelChange)="handleToggle(notification)"
/>
</tr>
} @empty {
<tr>
<td colspan="5">You have no notifications</td>
</tr>
}
} @else {
@for (row of ['', '']; track $index) {
<tr>
<td colspan="5"><div class="tui-skeleton">Loading</div></td>
</tr>
}
}
</tbody>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
CommonModule,
TuiForModule,
TuiCheckboxModule,
FormsModule,
TuiCheckboxModule,
TuiLineClampModule,
NotificationItemComponent,
],

View File

@@ -27,9 +27,16 @@ import { SettingBtn } from '../settings.types'
<tui-icon *ngIf="button.routerLink" icon="tuiIconChevronRight" />
</ng-template>
`,
styles: [
':host:not(:last-child) { display: block; box-shadow: 0 1px var(--tui-clear); }',
],
styles: `
:host:not(:last-child) {
display: block;
box-shadow: 0 1px var(--tui-clear);
}
button {
cursor: pointer;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, TuiIconModule, TuiTitleModule, RouterLink],

View File

@@ -51,7 +51,6 @@ import { SettingsUpdateComponent } from './update.component'
display: flex;
flex-direction: column;
gap: 1rem;
padding-top: 1rem;
}
`,
],

View File

@@ -47,10 +47,20 @@ import { UPDATE } from '../modals/update.component'
</div>
</button>
`,
styles: [
':host { display: block; box-shadow: 0 1px var(--tui-clear); }',
'.small { width: 1rem; height: 1rem; }',
],
styles: `
:host {
display: block;
box-shadow: 0 1px var(--tui-clear);
}
button {
cursor: pointer;
}
.small {
font-size: 1rem;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, TuiIconModule, TuiTitleModule],

View File

@@ -9,44 +9,29 @@ import { SettingsMenuComponent } from './components/menu.component'
routerLink="/portal/system/settings"
routerLinkActive="_current"
[routerLinkActiveOptions]="{ exact: true }"
>
<tui-icon icon="tuiIconChevronLeft" />
Settings
</a>
<settings-menu class="page" />
></a>
<settings-menu />
<router-outlet />
`,
styles: [
`
:host {
padding-top: 1rem;
::ng-deep tui-notification {
position: sticky;
left: 0;
}
}
a {
position: sticky;
left: 0;
display: inline-flex;
align-items: center;
gap: 0.5rem;
margin: 1rem 0;
font-size: 1rem;
color: var(--tui-text-01);
}
._current {
a,
settings-menu {
display: none;
}
.page {
display: none;
}
._current + .page {
._current + settings-menu {
display: flex;
max-width: 45rem;
max-width: 30rem;
margin: 0 auto;
}
`,

View File

@@ -48,6 +48,12 @@ const ROUTES: Routes = [
loadComponent: () => import('./updates/updates.component'),
data: toNavigationItem('/portal/system/updates'),
},
{
title: systemTabResolver,
path: 'metrics',
loadComponent: () => import('./metrics/metrics.component'),
data: toNavigationItem('/portal/system/metrics'),
},
]
@NgModule({ imports: [RouterModule.forChild(ROUTES)] })

View File

@@ -43,7 +43,9 @@ import { hasCurrentDeps } from 'src/app/utils/has-deps'
template: `
<tui-accordion-item borders="top-bottom">
<div class="g-action">
<tui-avatar size="s" [src]="marketplacePkg" />
<tui-avatar size="s">
<img alt="" [src]="marketplacePkg.icon" />
</tui-avatar>
<div [style.flex]="1" [style.overflow]="'hidden'">
<strong>{{ marketplacePkg.manifest.title }}</strong>
<div>