fix: fix discussed issues (#2467)

* fix: fix discussed issues

* chore: fix issues

* fix package lock

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
This commit is contained in:
Alex Inkin
2023-10-23 02:49:05 +04:00
committed by GitHub
parent 8034e5bbcb
commit b195e3435f
16 changed files with 3046 additions and 1556 deletions

View File

@@ -24,6 +24,7 @@ import { ThemeSwitcherService } from './services/theme-switcher.service'
import { DateTransformerService } from './services/date-transformer.service'
import { DatetimeTransformerService } from './services/datetime-transformer.service'
import { MarketplaceService } from './services/marketplace.service'
import { RoutingStrategyService } from './apps/portal/services/routing-strategy.service'
const {
useMocks,
@@ -79,6 +80,10 @@ export const APP_PROVIDERS: Provider[] = [
provide: AbstractMarketplaceService,
useClass: MarketplaceService,
},
{
provide: RouteReuseStrategy,
useExisting: RoutingStrategyService,
},
]
export function appInitializer(

View File

@@ -1,8 +1,8 @@
<span class="link">
<tui-badged-content [style.--tui-radius.rem]="1.5">
<tui-badge-alert *ngIf="badge" size="m" tuiSlot="top">
<tui-badge-notification *ngIf="badge" size="m" tuiSlot="top">
{{ badge }}
</tui-badge-alert>
</tui-badge-notification>
<tui-svg
*ngIf="icon.startsWith('tuiIcon'); else url"
class="icon"

View File

@@ -7,8 +7,8 @@ import {
Input,
} from '@angular/core'
import {
TuiBadgeAlertModule,
TuiBadgedContentModule,
TuiBadgeNotificationModule,
} from '@taiga-ui/experimental'
import { RouterLink } from '@angular/router'
import { TickerModule } from '@start9labs/shared'
@@ -37,7 +37,7 @@ import { toRouterLink } from '../../utils/to-router-link'
TuiSvgModule,
TickerModule,
TuiBadgedContentModule,
TuiBadgeAlertModule,
TuiBadgeNotificationModule,
ActionsComponent,
],
})

View File

@@ -1,18 +1,28 @@
@import '@taiga-ui/core/styles/taiga-ui-local';
:host {
@include scrollbar-hidden;
height: 3rem;
display: flex;
// TODO: Theme
background: rgb(97 95 95 / 84%);
overflow: auto;
}
.tab {
position: relative;
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
width: 7.5rem;
&_active {
position: sticky;
left: 0;
right: 0;
z-index: 1;
// TODO: Theme
background: #373a3f;
}

View File

@@ -1,6 +1,6 @@
import { CommonModule, Location } from '@angular/common'
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { RouterModule } from '@angular/router'
import { Router, RouterModule } from '@angular/router'
import { TuiButtonModule, TuiSvgModule } from '@taiga-ui/core'
import { NavigationService } from '../../services/navigation.service'
import { NavigationItem } from '../../types/navigation-item'
@@ -14,7 +14,7 @@ import { NavigationItem } from '../../types/navigation-item'
imports: [CommonModule, RouterModule, TuiButtonModule, TuiSvgModule],
})
export class NavigationComponent {
private readonly location = inject(Location)
private readonly router = inject(Router)
private readonly navigation = inject(NavigationService)
readonly tabs$ = this.navigation.getTabs()
@@ -22,6 +22,6 @@ export class NavigationComponent {
removeTab(tab: NavigationItem, active: boolean) {
this.navigation.removeTab(tab)
if (active) this.location.back()
if (active) this.router.navigate(['./portal/desktop'])
}
}

View File

@@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
import { TuiDialogService, TuiSvgModule } from '@taiga-ui/core'
import { TuiFadeModule } from '@taiga-ui/experimental'
import { BackupsCreateService } from './services/create.service'
import { BackupsRestoreService } from './services/restore.service'
import { BackupsUpcomingComponent } from './components/upcoming.component'
@@ -24,15 +25,20 @@ import { JOBS } from './modals/jobs.component'
</div>
</button>
</section>
<section>
<h3 class="g-title">Upcoming Jobs</h3>
<h3 class="g-title">Upcoming Jobs</h3>
<div tuiFade class="g-hidden-scrollbar">
<table backupsUpcoming class="g-table"></table>
</section>
</div>
`,
host: { class: 'g-page' },
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [CommonModule, TuiSvgModule, BackupsUpcomingComponent],
imports: [
CommonModule,
TuiSvgModule,
TuiFadeModule,
BackupsUpcomingComponent,
],
})
export class BackupsComponent {
private readonly dialogs = inject(TuiDialogService)
@@ -64,7 +70,9 @@ export class BackupsComponent {
icon: 'tuiIconDatabaseLarge',
description: 'Manage backup targets',
action: () =>
this.dialogs.open(TARGETS, { label: 'Backup Targets' }).subscribe(),
this.dialogs
.open(TARGETS, { label: 'Backup Targets', size: 'l' })
.subscribe(),
},
{
name: 'History',

View File

@@ -14,6 +14,7 @@ import {
TuiLinkModule,
TuiSvgModule,
} from '@taiga-ui/core'
import { TuiFadeModule } from '@taiga-ui/experimental'
import { TuiCheckboxModule } from '@taiga-ui/kit'
import { BehaviorSubject } from 'rxjs'
import { BackupRun } from 'src/app/services/api/api.types'
@@ -38,68 +39,70 @@ import { REPORT } from './report.component'
Delete Selected
</button>
</h3>
<table class="g-table">
<thead>
<tr>
<th>
<tui-checkbox
[disabled]="!selected.length"
[ngModel]="all"
(ngModelChange)="toggle()"
></tui-checkbox>
</th>
<th>Started At</th>
<th>Duration</th>
<th>Result</th>
<th>Job</th>
<th>Target</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="
let run of runs;
let index = index;
else: loading;
empty: blank
"
[style.background]="selected[index] ? 'var(--tui-clear)' : ''"
>
<td><tui-checkbox [(ngModel)]="selected[index]"></tui-checkbox></td>
<td>{{ run['started-at'] | date : 'medium' }}</td>
<td>
{{ run['started-at'] | duration : run['completed-at'] }} Minutes
</td>
<td>
<tui-svg
*ngIf="run.report | hasError; else noError"
src="tuiIconClose"
[style.color]="'var(--tui-negative)'"
></tui-svg>
<ng-template #noError>
<tui-svg
src="tuiIconCheck"
[style.color]="'var(--tui-positive)'"
></tui-svg>
</ng-template>
<button tuiLink (click)="showReport(run)">Report</button>
</td>
<td>{{ run.job.name || 'No job' }}</td>
<td>
<tui-svg [src]="run.job.target.type | getBackupIcon"></tui-svg>
{{ run.job.target.name }}
</td>
</tr>
<ng-template #loading>
<tr *ngFor="let row of ['', '', '']">
<td colspan="6"><div class="tui-skeleton">Loading</div></td>
<div tuiFade class="g-hidden-scrollbar">
<table class="g-table">
<thead>
<tr>
<th>
<tui-checkbox
[disabled]="!selected.length"
[ngModel]="all"
(ngModelChange)="toggle()"
></tui-checkbox>
</th>
<th>Started At</th>
<th>Duration</th>
<th>Result</th>
<th>Job</th>
<th>Target</th>
</tr>
</ng-template>
<ng-template #blank>
<tr><td colspan="6">No backups have been run yet.</td></tr>
</ng-template>
</tbody>
</table>
</thead>
<tbody>
<tr
*ngFor="
let run of runs;
let index = index;
else: loading;
empty: blank
"
[style.background]="selected[index] ? 'var(--tui-clear)' : ''"
>
<td><tui-checkbox [(ngModel)]="selected[index]"></tui-checkbox></td>
<td>{{ run['started-at'] | date : 'medium' }}</td>
<td>
{{ run['started-at'] | duration : run['completed-at'] }} Minutes
</td>
<td>
<tui-svg
*ngIf="run.report | hasError; else noError"
src="tuiIconClose"
[style.color]="'var(--tui-negative)'"
></tui-svg>
<ng-template #noError>
<tui-svg
src="tuiIconCheck"
[style.color]="'var(--tui-positive)'"
></tui-svg>
</ng-template>
<button tuiLink (click)="showReport(run)">Report</button>
</td>
<td>{{ run.job.name || 'No job' }}</td>
<td>
<tui-svg [src]="run.job.target.type | getBackupIcon"></tui-svg>
{{ run.job.target.name }}
</td>
</tr>
<ng-template #loading>
<tr *ngFor="let row of ['', '', '']">
<td colspan="6"><div class="tui-skeleton">Loading</div></td>
</tr>
</ng-template>
<ng-template #blank>
<tr><td colspan="6">No backups have been run yet.</td></tr>
</ng-template>
</tbody>
</table>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
@@ -111,6 +114,7 @@ import { REPORT } from './report.component'
TuiCheckboxModule,
TuiSvgModule,
TuiLinkModule,
TuiFadeModule,
DurationPipe,
HasErrorPipe,
GetBackupIconPipe,

View File

@@ -9,6 +9,7 @@ import {
TuiNotificationModule,
TuiSvgModule,
} from '@taiga-ui/core'
import { TuiFadeModule } from '@taiga-ui/experimental'
import { TUI_PROMPT, TuiPromptData } from '@taiga-ui/kit'
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
import { BehaviorSubject, filter } from 'rxjs'
@@ -39,52 +40,54 @@ import { EDIT } from './edit.component'
Create New Job
</button>
</h3>
<table class="g-table">
<thead>
<tr>
<th>Name</th>
<th>Target</th>
<th>Packages</th>
<th>Schedule</th>
<th [style.width.rem]="3.5"></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let job of jobs || null; else: loading; empty: blank">
<td>{{ job.name }}</td>
<td>
<tui-svg [src]="job.target.type | getBackupIcon"></tui-svg>
{{ job.target.name }}
</td>
<td>Packages: {{ job['package-ids'].length }}</td>
<td>{{ (job.cron | toHumanCron).message }}</td>
<td>
<button
tuiIconButton
appearance="icon"
size="xs"
icon="tuiIconEdit2"
(click)="update(job)"
></button>
<button
tuiIconButton
appearance="icon"
size="xs"
icon="tuiIconTrash2"
(click)="delete(job.id)"
></button>
</td>
</tr>
<ng-template #loading>
<tr *ngFor="let i of ['', '']">
<td colspan="5"><div class="tui-skeleton">Loading</div></td>
<div class="g-hidden-scrollbar" tuiFade>
<table class="g-table">
<thead>
<tr>
<th>Name</th>
<th>Target</th>
<th>Packages</th>
<th>Schedule</th>
<th [style.width.rem]="3.5"></th>
</tr>
</ng-template>
<ng-template #blank>
<tr><td colspan="5">No jobs found.</td></tr>
</ng-template>
</tbody>
</table>
</thead>
<tbody>
<tr *ngFor="let job of jobs || null; else: loading; empty: blank">
<td>{{ job.name }}</td>
<td>
<tui-svg [src]="job.target.type | getBackupIcon"></tui-svg>
{{ job.target.name }}
</td>
<td>Packages: {{ job['package-ids'].length }}</td>
<td>{{ (job.cron | toHumanCron).message }}</td>
<td>
<button
tuiIconButton
appearance="icon"
size="xs"
icon="tuiIconEdit2"
(click)="update(job)"
></button>
<button
tuiIconButton
appearance="icon"
size="xs"
icon="tuiIconTrash2"
(click)="delete(job.id)"
></button>
</td>
</tr>
<ng-template #loading>
<tr *ngFor="let i of ['', '']">
<td colspan="5"><div class="tui-skeleton">Loading</div></td>
</tr>
</ng-template>
<ng-template #blank>
<tr><td colspan="5">No jobs found.</td></tr>
</ng-template>
</tbody>
</table>
</div>
`,
standalone: true,
imports: [
@@ -93,6 +96,7 @@ import { EDIT } from './edit.component'
TuiNotificationModule,
TuiButtonModule,
TuiSvgModule,
TuiFadeModule,
ToHumanCronPipe,
GetBackupIconPipe,
],

View File

@@ -107,8 +107,10 @@ export class BackupsTargetModal {
}
goToTargets() {
this.dialogs.open(TARGETS, { label: 'Backup Targets' }).subscribe()
this.context.$implicit.complete()
this.dialogs
.open(TARGETS, { label: 'Backup Targets', size: 'l' })
.subscribe()
}
}

View File

@@ -37,6 +37,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { BackupConfig } from '../types/backup-config'
import { BackupsPhysicalComponent } from '../components/physical.component'
import { BackupsTargetsComponent } from '../components/targets.component'
import { TuiFadeModule } from '@taiga-ui/experimental'
@Component({
template: `
@@ -60,23 +61,27 @@ import { BackupsTargetsComponent } from '../components/targets.component'
Refresh
</button>
</h3>
<table
class="g-table"
[backupsPhysical]="targets?.['unknown-disks'] || null"
(add)="addPhysical($event)"
></table>
<div class="g-hidden-scrollbar" tuiFade>
<table
class="g-table"
[backupsPhysical]="targets?.['unknown-disks'] || null"
(add)="addPhysical($event)"
></table>
</div>
<h3 class="g-title">
Saved Targets
<button tuiButton size="s" icon="tuiIconPlus" (click)="addRemote()">
Add Target
</button>
</h3>
<table
class="g-table"
[backupsTargets]="targets?.saved || null"
(delete)="onDelete($event)"
(update)="onUpdate($event)"
></table>
<div class="g-hidden-scrollbar" tuiFade>
<table
class="g-table"
[backupsTargets]="targets?.saved || null"
(delete)="onDelete($event)"
(update)="onUpdate($event)"
></table>
</div>
`,
standalone: true,
imports: [
@@ -85,6 +90,7 @@ import { BackupsTargetsComponent } from '../components/targets.component'
TuiButtonModule,
BackupsPhysicalComponent,
BackupsTargetsComponent,
TuiFadeModule,
],
})
export class BackupsTargetsModal implements OnInit {
@@ -103,6 +109,7 @@ export class BackupsTargetsModal implements OnInit {
async refresh() {
this.loading$.next(true)
this.targets = undefined
try {
this.targets = await this.api.getBackupTargets({})

View File

@@ -46,7 +46,7 @@ import { InstallProgressPipe } from '../pipes/install-progress.pipe'
<tui-accordion-item borders="top-bottom">
<div class="g-action">
<tui-avatar size="s" [src]="pkg | mimeType | trustUrl" />
<div [style.flex]="1">
<div [style.flex]="1" [style.overflow]="'hidden'">
<strong>{{ pkg.manifest.title }}</strong>
<div>
<!-- @TODO left side should be local['old-manifest'] (or whatever), not manifest. -->

View File

@@ -6,7 +6,7 @@ import { NavigationItem } from '../types/navigation-item'
providedIn: 'root',
})
export class NavigationService {
readonly tabs = new BehaviorSubject<readonly NavigationItem[]>([])
private readonly tabs = new BehaviorSubject<readonly NavigationItem[]>([])
getTabs(): Observable<readonly NavigationItem[]> {
return this.tabs
@@ -21,4 +21,8 @@ export class NavigationService {
this.tabs.next([...this.tabs.value, tab])
}
}
hasTab(path: string): boolean {
return this.tabs.value.some(t => t.routerLink === path)
}
}

View File

@@ -0,0 +1,73 @@
import { inject, Injectable } from '@angular/core'
import {
ActivatedRouteSnapshot,
BaseRouteReuseStrategy,
createUrlTreeFromSnapshot,
DetachedRouteHandle,
UrlSerializer,
} from '@angular/router'
import { NavigationService } from './navigation.service'
@Injectable({
providedIn: 'root',
})
export class RoutingStrategyService extends BaseRouteReuseStrategy {
private readonly url = inject(UrlSerializer)
private readonly navigation = inject(NavigationService)
private readonly handlers = new Map<string, DetachedRouteHandle>()
override shouldDetach(route: ActivatedRouteSnapshot): boolean {
const path = this.getPath(route)
const store = this.navigation.hasTab(path)
if (!store) this.handlers.delete(path)
return store
}
override store(
route: ActivatedRouteSnapshot,
handle: DetachedRouteHandle,
): void {
this.handlers.set(this.getPath(route), handle)
}
override shouldAttach(route: ActivatedRouteSnapshot): boolean {
return !!this.handlers.get(this.getPath(route))
}
override retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
return this.handlers.get(this.getPath(route)) || null
}
override shouldReuseRoute(
future: ActivatedRouteSnapshot,
curr: ActivatedRouteSnapshot,
): boolean {
// return future.routeConfig === curr.routeConfig
// TODO: Copied from ionic for backwards compatibility, remove later
if (future.routeConfig !== curr.routeConfig) {
return false
}
// checking router params
const futureParams = future.params
const currentParams = curr.params
const keysA = Object.keys(futureParams)
const keysB = Object.keys(currentParams)
if (keysA.length !== keysB.length) {
return false
}
// Test for A's keys different from B.
for (const key of keysA) {
if (currentParams[key] !== futureParams[key]) {
return false
}
}
return true
}
private getPath(route: ActivatedRouteSnapshot): string {
return this.url.serialize(createUrlTreeFromSnapshot(route, ['.']))
}
}

View File

@@ -382,6 +382,7 @@ ul {
.g-table {
width: 100%;
min-width: 40rem;
td,
th {
@@ -467,3 +468,8 @@ button.g-action {
margin-left: auto;
}
}
.g-hidden-scrollbar {
@include scrollbar-hidden;
overflow: auto !important;
}