chore: enable strict mode (#1569)

* chore: enable strict mode

* refactor: remove sync data access from PatchDbService

* launchable even when no LAN url

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
Alex Inkin
2022-07-22 18:51:08 +03:00
committed by GitHub
parent 9a01a0df8e
commit 7b8a0eadf3
130 changed files with 1130 additions and 1045 deletions

View File

@@ -1,4 +1,4 @@
<h1>
<ion-text color="warning">Warning</ion-text>
</h1>
<div class="ion-text-left" [innerHTML]="params.message | markdown"></div>
<div class="ion-text-left" [innerHTML]="params.message || '' | markdown"></div>

View File

@@ -7,9 +7,8 @@ import { BaseSlide } from '../wizard-types'
styleUrls: ['../app-wizard.component.scss'],
})
export class AlertComponent implements BaseSlide {
@Input() params: {
message: string
}
@Input()
params!: { message: string }
async load() {}

View File

@@ -22,7 +22,8 @@ SwiperCore.use([IonicSlides])
styleUrls: ['./app-wizard.component.scss'],
})
export class AppWizardComponent {
@Input() params: {
@Input()
params!: {
action: WizardAction
title: string
slides: SlideDefinition[]
@@ -31,16 +32,17 @@ export class AppWizardComponent {
}
// content container so we can scroll to top between slide transitions
@ViewChild(IonContent) content: IonContent
@ViewChild(IonContent)
content?: IonContent
swiper: Swiper
swiper?: Swiper
//a slide component gives us hook into a slide. Allows us to call load when slide comes into view
@ViewChildren('components')
slideComponentsQL: QueryList<BaseSlide>
slideComponentsQL?: QueryList<BaseSlide>
get slideComponents(): BaseSlide[] {
return this.slideComponentsQL.toArray()
return this.slideComponentsQL?.toArray() || []
}
get currentSlide(): BaseSlide {
@@ -48,7 +50,7 @@ export class AppWizardComponent {
}
get currentIndex(): number {
return this.swiper.activeIndex
return this.swiper?.activeIndex || NaN
}
initializing = true
@@ -58,7 +60,7 @@ export class AppWizardComponent {
ionViewDidEnter() {
this.initializing = false
this.swiper.allowTouchMove = false
if (this.swiper) this.swiper.allowTouchMove = false
this.loadSlide()
}
@@ -71,8 +73,8 @@ export class AppWizardComponent {
}
async next() {
await this.content.scrollToTop()
this.swiper.slideNext(500)
await this.content?.scrollToTop()
this.swiper?.slideNext(500)
}
setError(e: any) {

View File

@@ -8,7 +8,8 @@ import { BaseSlide } from '../wizard-types'
styleUrls: ['../app-wizard.component.scss'],
})
export class CompleteComponent implements BaseSlide {
@Input() params: {
@Input()
params!: {
verb: string // loader verb: '*stopping* ...'
title: string
Fn: () => Promise<any>
@@ -17,13 +18,13 @@ export class CompleteComponent implements BaseSlide {
@Output() onSuccess: EventEmitter<void> = new EventEmitter()
@Output() onError: EventEmitter<string> = new EventEmitter()
message: string
message = ''
loading = true
async load() {
this.message =
capitalizeFirstLetter(this.params.verb) + ' ' + this.params.title
capitalizeFirstLetter(this.params.verb || '') + ' ' + this.params.title
try {
await this.params.Fn()
this.onSuccess.emit()

View File

@@ -10,7 +10,8 @@ import { BaseSlide } from '../wizard-types'
styleUrls: ['./dependents.component.scss', '../app-wizard.component.scss'],
})
export class DependentsComponent implements BaseSlide {
@Input() params: {
@Input()
params!: {
title: string
verb: string // *Uninstalling* will cause problems...
Fn: () => Promise<Breakages>
@@ -19,21 +20,21 @@ export class DependentsComponent implements BaseSlide {
@Output() onSuccess: EventEmitter<void> = new EventEmitter()
@Output() onError: EventEmitter<string> = new EventEmitter()
breakages: Breakages
warningMessage: string | undefined
breakages?: Breakages
warningMessage = ''
loading = true
readonly pkgs$ = this.patch.watch$('package-data')
constructor(public readonly patch: PatchDbService) {}
constructor(private readonly patch: PatchDbService) {}
async load() {
try {
this.breakages = await this.params.Fn()
if (this.breakages && !isEmptyObject(this.breakages)) {
this.warningMessage =
capitalizeFirstLetter(this.params.verb) +
capitalizeFirstLetter(this.params.verb || '') +
' ' +
this.params.title +
' will prohibit the following services from functioning properly.'

View File

@@ -7,7 +7,7 @@
type === 'create' ? 'Create Backup' : 'Restore From Backup'
}}</ion-title>
<ion-buttons slot="end">
<ion-button [disabled]="backupService.loading" (click)="refresh()">
<ion-button [disabled]="loading" (click)="refresh()">
Refresh
<ion-icon slot="end" name="refresh"></ion-icon>
</ion-button>

View File

@@ -3,17 +3,17 @@
<ion-content class="ion-padding">
<!-- loading -->
<text-spinner
*ngIf="backupService.loading; else loaded"
*ngIf="loading; else loaded"
[text]="loadingText"
></text-spinner>
<!-- loaded -->
<ng-template #loaded>
<!-- error -->
<ion-item *ngIf="backupService.loadingError; else noError">
<ion-item *ngIf="loadingError; else noError">
<ion-label>
<ion-text color="danger">
{{ backupService.loadingError }}
{{ loadingError }}
</ion-text>
</ion-label>
</ion-item>
@@ -49,7 +49,7 @@
</ion-label>
</ion-item>
<!-- cifs list -->
<ng-container *ngFor="let target of backupService.cifs; let i = index">
<ng-container *ngFor="let target of cifs; let i = index">
<ion-item
button
*ngIf="target.entry as cifs"
@@ -91,7 +91,7 @@
<ion-item-divider>Physical Drives</ion-item-divider>
<!-- no drives -->
<ion-item
*ngIf="!backupService.drives.length; else hasDrives"
*ngIf="!drives.length; else hasDrives"
class="ion-padding-bottom"
>
<ion-label>
@@ -119,7 +119,7 @@
<ng-template #hasDrives>
<ion-item
button
*ngFor="let target of backupService.drives"
*ngFor="let target of drives"
(click)="select(target)"
>
<ion-icon slot="start" name="save-outline" size="large"></ion-icon>

View File

@@ -25,11 +25,11 @@ type BackupType = 'create' | 'restore'
styleUrls: ['./backup-drives.component.scss'],
})
export class BackupDrivesComponent {
@Input() type: BackupType
@Input() type!: BackupType
@Output() onSelect: EventEmitter<
MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>
> = new EventEmitter()
loadingText: string
loadingText = ''
constructor(
private readonly loadingCtrl: LoadingController,
@@ -38,9 +38,25 @@ export class BackupDrivesComponent {
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
private readonly errToast: ErrorToastService,
public readonly backupService: BackupService,
private readonly backupService: BackupService,
) {}
get loading() {
return this.backupService.loading
}
get loadingError() {
return this.backupService.loadingError
}
get drives() {
return this.backupService.drives
}
get cifs() {
return this.backupService.cifs
}
ngOnInit() {
this.loadingText =
this.type === 'create'
@@ -234,10 +250,14 @@ export class BackupDrivesComponent {
styleUrls: ['./backup-drives.component.scss'],
})
export class BackupDrivesHeaderComponent {
@Input() type: BackupType
@Input() type!: BackupType
@Output() onClose: EventEmitter<void> = new EventEmitter()
constructor(public readonly backupService: BackupService) {}
constructor(private readonly backupService: BackupService) {}
get loading() {
return this.backupService.loading
}
refresh() {
this.backupService.getBackupTargets()
@@ -250,8 +270,8 @@ export class BackupDrivesHeaderComponent {
styleUrls: ['./backup-drives.component.scss'],
})
export class BackupDrivesStatusComponent {
@Input() type: string
@Input() hasValidBackup: boolean
@Input() type!: BackupType
@Input() hasValidBackup!: boolean
}
const CifsSpec: ConfigSpec = {

View File

@@ -13,10 +13,10 @@ import { getErrorMessage, Emver } from '@start9labs/shared'
providedIn: 'root',
})
export class BackupService {
cifs: MappedBackupTarget<CifsBackupTarget>[]
drives: MappedBackupTarget<DiskBackupTarget>[]
cifs: MappedBackupTarget<CifsBackupTarget>[] = []
drives: MappedBackupTarget<DiskBackupTarget>[] = []
loading = true
loadingError: string | IonicSafeString
loadingError: string | IonicSafeString = ''
constructor(
private readonly embassyApi: ApiService,

View File

@@ -8,32 +8,30 @@ import { combineLatest, Subscription } from 'rxjs'
templateUrl: './badge-menu.component.html',
styleUrls: ['./badge-menu.component.scss'],
})
export class BadgeMenuComponent {
unreadCount: number
sidebarOpen: boolean
unreadCount = 0
sidebarOpen = false
subs: Subscription[] = []
constructor (
constructor(
private readonly splitPane: SplitPaneTracker,
private readonly patch: PatchDbService,
) { }
) {}
ngOnInit () {
ngOnInit() {
this.subs = [
combineLatest([
this.patch.watch$('server-info', 'unread-notification-count'),
this.splitPane.sidebarOpen$,
])
.subscribe(([unread, menu]) => {
]).subscribe(([unread, menu]) => {
this.unreadCount = unread
this.sidebarOpen = menu
}),
]
}
ngOnDestroy () {
ngOnDestroy() {
this.subs.forEach(sub => sub.unsubscribe())
}
}

View File

@@ -18,8 +18,9 @@
(['string', 'number'] | includes: data.spec.type) &&
!$any(data.spec).nullable
"
>&nbsp;*</span
>
&nbsp;*
</span>
<span *ngIf="data.spec.type === 'list' && Range.from(data.spec.range).min"
>&nbsp;*</span

View File

@@ -35,8 +35,8 @@ interface Config {
styleUrls: ['./form-object.component.scss'],
})
export class FormObjectComponent {
@Input() objectSpec: ConfigSpec
@Input() formGroup: FormGroup
@Input() objectSpec!: ConfigSpec
@Input() formGroup!: FormGroup
@Input() unionSpec?: ValueSpecUnion
@Input() current?: Config
@Input() original?: Config
@@ -396,7 +396,7 @@ interface HeaderData {
})
export class FormLabelComponent {
Range = Range
@Input() data: HeaderData
@Input() data!: HeaderData
constructor(private readonly alertCtrl: AlertController) {}
@@ -424,6 +424,6 @@ export class FormLabelComponent {
styleUrls: ['./form-object.component.scss'],
})
export class FormErrorComponent {
@Input() control: AbstractFormGroupDirective
@Input() spec: ValueSpec
@Input() control!: AbstractFormGroupDirective
@Input() spec!: ValueSpec
}

View File

@@ -9,7 +9,7 @@
*ngIf="!loading && needInfinite"
position="top"
threshold="0"
(ionInfinite)="loadData($event)"
(ionInfinite)="doInfinite($event)"
>
<ion-infinite-scroll-content
loadingSpinner="lines"
@@ -25,11 +25,11 @@
></div>
</div>
<div id="button-div" *ngIf="!loading" style="width: 100%; text-align: center">
<ion-button *ngIf="!loadingMore" (click)="loadMore()" strong color="dark">
<ion-button *ngIf="!loadingNext" (click)="getNext()" strong color="dark">
Load More
<ion-icon slot="end" name="refresh"></ion-icon>
</ion-button>
<ion-spinner *ngIf="loadingMore" name="lines" color="warning"></ion-spinner>
<ion-spinner *ngIf="loadingNext" name="lines" color="warning"></ion-spinner>
</div>
<div

View File

@@ -17,29 +17,91 @@ var convert = new Convert({
styleUrls: ['./logs.page.scss'],
})
export class LogsPage {
@ViewChild(IonContent) private content: IonContent
@Input() fetchLogs: (params: {
@ViewChild(IonContent)
private content?: IonContent
@Input()
fetchLogs!: (params: {
before_flag?: boolean
limit?: number
cursor?: string
}) => Promise<RR.LogsRes>
loading = true
loadingMore = false
logs: string
loadingNext = false
needInfinite = true
startCursor: string
endCursor: string
limit = 200
scrollToBottomButton = false
startCursor?: string
endCursor?: string
limit = 400
isOnBottom = true
constructor(private readonly errToast: ErrorToastService) {}
ngOnInit() {
this.getLogs()
async ngOnInit() {
await this.getPrior()
this.loading = false
}
async fetch(isBefore: boolean = true) {
async getNext() {
this.loadingNext = true
const logs = await this.fetch(false)
if (!logs?.length) return (this.loadingNext = false)
const container = document.getElementById('container')
const newLogs = document.getElementById('template')?.cloneNode(true)
if (!(newLogs instanceof HTMLElement)) return
newLogs.innerHTML =
logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') +
(logs.length ? '\n' : '')
container?.append(newLogs)
this.loadingNext = false
this.scrollEvent()
}
async doInfinite(e: any): Promise<void> {
await this.getPrior()
e.target.complete()
}
scrollEvent() {
const buttonDiv = document.getElementById('button-div')
this.isOnBottom =
!!buttonDiv && buttonDiv.getBoundingClientRect().top < window.innerHeight
}
scrollToBottom() {
this.content?.scrollToBottom(500)
}
private async getPrior() {
// get logs
const logs = await this.fetch()
if (!logs?.length) return
const container = document.getElementById('container')
const beforeContainerHeight = container?.scrollHeight || 0
const newLogs = document.getElementById('template')?.cloneNode(true)
if (!(newLogs instanceof HTMLElement)) return
newLogs.innerHTML =
logs.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`).join('\n') +
(logs.length ? '\n' : '')
container?.prepend(newLogs)
const afterContainerHeight = container?.scrollHeight || 0
// scroll down
scrollBy(0, afterContainerHeight - beforeContainerHeight)
this.content?.scrollToPoint(0, afterContainerHeight - beforeContainerHeight)
if (logs.length < this.limit) {
this.needInfinite = false
}
}
private async fetch(isBefore: boolean = true) {
try {
const cursor = isBefore ? this.startCursor : this.endCursor
const logsRes = await this.fetchLogs({
@@ -55,79 +117,10 @@ export class LogsPage {
if ((!isBefore || !this.endCursor) && logsRes['end-cursor']) {
this.endCursor = logsRes['end-cursor']
}
this.loading = false
return logsRes.entries
} catch (e: any) {
this.errToast.present(e)
}
}
async getLogs() {
try {
// get logs
const logs = await this.fetch()
if (!logs?.length) return
const container = document.getElementById('container')
const beforeContainerHeight = container?.scrollHeight || 0
const newLogs = document.getElementById('template')?.cloneNode(true)
if (!(newLogs instanceof HTMLElement)) return
newLogs.innerHTML =
logs
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
.join('\n') + (logs.length ? '\n' : '')
container?.prepend(newLogs)
const afterContainerHeight = container?.scrollHeight || 0
// scroll down
scrollBy(0, afterContainerHeight - beforeContainerHeight)
this.content.scrollToPoint(
0,
afterContainerHeight - beforeContainerHeight,
)
if (logs.length < this.limit) {
this.needInfinite = false
}
} catch (e) {}
}
async loadMore() {
try {
this.loadingMore = true
const logs = await this.fetch(false)
if (!logs?.length) return (this.loadingMore = false)
const container = document.getElementById('container')
const newLogs = document.getElementById('template')?.cloneNode(true)
if (!(newLogs instanceof HTMLElement)) return
newLogs.innerHTML =
logs
.map(l => `${l.timestamp} ${convert.toHtml(l.message)}`)
.join('\n') + (logs.length ? '\n' : '')
container?.append(newLogs)
this.loadingMore = false
this.scrollEvent()
} catch (e) {}
}
scrollEvent() {
const buttonDiv = document.getElementById('button-div')
this.isOnBottom =
!!buttonDiv && buttonDiv.getBoundingClientRect().top < window.innerHeight
}
scrollToBottom() {
this.content.scrollToBottom(500)
}
async loadData(e: any): Promise<void> {
await this.getLogs()
e.target.complete()
}
}

View File

@@ -6,5 +6,5 @@ import { Component, Input } from '@angular/core'
styleUrls: ['./qr.component.scss'],
})
export class QRComponent {
@Input() text: string
@Input() text!: string
}

View File

@@ -1,20 +1,18 @@
import { Component, Input } from '@angular/core'
import { Component, Input, OnChanges } from '@angular/core'
@Component({
selector: 'skeleton-list',
templateUrl: './skeleton-list.component.html',
styleUrls: ['./skeleton-list.component.scss'],
})
export class SkeletonListComponent {
@Input() groups: string
@Input() rows: string = '3'
export class SkeletonListComponent implements OnChanges {
@Input() groups = 0
@Input() rows = 3
groupsArr: number[] = []
rowsArr: number[] = []
ngOnInit () {
if (this.groups) {
this.groupsArr = Array(Number(this.groups)).fill(0).map((_, i) => i)
}
this.rowsArr = Array(Number(this.rows)).fill(0).map((_, i) => i)
ngOnChanges() {
this.groupsArr = Array(this.groups).fill(0)
this.rowsArr = Array(this.rows).fill(0)
}
}

View File

@@ -16,7 +16,7 @@ export class StatusComponent {
PS = PrimaryStatus
PR = PrimaryRendering
@Input() rendering: StatusRendering
@Input() rendering!: StatusRendering
@Input() size?: string
@Input() style?: string = 'regular'
@Input() weight?: string = 'normal'