mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
chore: cleanup - show spinner on service list when transitioning
config add new list items to end and auto scroll remove js engine artifacts fix view button in notification toast
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@
|
|||||||
/*_product_key.txt
|
/*_product_key.txt
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
deploy_web.sh
|
deploy_web.sh
|
||||||
|
libs/js_engine/src/artifacts/ARM_JS_SNAPSHOT.bin
|
||||||
|
libs/js_engine/src/artifacts/JS_SNAPSHOT.bin
|
||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
ToastController,
|
ToastController,
|
||||||
ToastOptions,
|
ToastOptions,
|
||||||
} from '@ionic/angular'
|
} from '@ionic/angular'
|
||||||
import { EMPTY, merge, Observable } from 'rxjs'
|
import { EMPTY, merge, Observable, ObservableInput } from 'rxjs'
|
||||||
import { filter, pairwise, switchMap, tap } from 'rxjs/operators'
|
import { filter, pairwise, switchMap, tap } from 'rxjs/operators'
|
||||||
import { ErrorToastService } from '@start9labs/shared'
|
import { ErrorToastService } from '@start9labs/shared'
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
|||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { PatchDataService } from './patch-data.service'
|
import { PatchDataService } from './patch-data.service'
|
||||||
|
import { DataModel, ServerInfo } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
// Watch unread notification count to display toast
|
// Watch unread notification count to display toast
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -20,7 +21,7 @@ export class UnreadToastService extends Observable<unknown> {
|
|||||||
private unreadToast: HTMLIonToastElement
|
private unreadToast: HTMLIonToastElement
|
||||||
|
|
||||||
private readonly stream$ = this.patchData.pipe(
|
private readonly stream$ = this.patchData.pipe(
|
||||||
switchMap(data => {
|
switchMap<DataModel | null, ObservableInput<number>>(data => {
|
||||||
if (data) {
|
if (data) {
|
||||||
return this.patch.watch$('server-info', 'unread-notification-count')
|
return this.patch.watch$('server-info', 'unread-notification-count')
|
||||||
}
|
}
|
||||||
@@ -36,6 +37,29 @@ export class UnreadToastService extends Observable<unknown> {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TOAST: ToastOptions = {
|
||||||
|
header: 'Embassy',
|
||||||
|
message: `New notifications`,
|
||||||
|
position: 'bottom',
|
||||||
|
duration: 4000,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
side: 'start',
|
||||||
|
icon: 'close',
|
||||||
|
handler: () => true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
side: 'end',
|
||||||
|
text: 'View',
|
||||||
|
handler: () => {
|
||||||
|
this.router.navigate(['/notifications'], {
|
||||||
|
queryParams: { toast: true },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly router: Router,
|
private readonly router: Router,
|
||||||
private readonly patchData: PatchDataService,
|
private readonly patchData: PatchDataService,
|
||||||
@@ -50,31 +74,9 @@ export class UnreadToastService extends Observable<unknown> {
|
|||||||
private async showToast() {
|
private async showToast() {
|
||||||
await this.unreadToast?.dismiss()
|
await this.unreadToast?.dismiss()
|
||||||
|
|
||||||
this.unreadToast = await this.toastCtrl.create(TOAST)
|
this.unreadToast = await this.toastCtrl.create(this.TOAST)
|
||||||
this.unreadToast.buttons?.push({
|
this.unreadToast.buttons?.push()
|
||||||
side: 'end',
|
|
||||||
text: 'View',
|
|
||||||
handler: () => {
|
|
||||||
this.router.navigate(['/notifications'], {
|
|
||||||
queryParams: { toast: true },
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await this.unreadToast.present()
|
await this.unreadToast.present()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOAST: ToastOptions = {
|
|
||||||
header: 'Embassy',
|
|
||||||
message: `New notifications`,
|
|
||||||
position: 'bottom',
|
|
||||||
duration: 4000,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
side: 'start',
|
|
||||||
icon: 'close',
|
|
||||||
handler: () => true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const ICONS = [
|
|||||||
'alert-circle-outline',
|
'alert-circle-outline',
|
||||||
'aperture-outline',
|
'aperture-outline',
|
||||||
'arrow-back',
|
'arrow-back',
|
||||||
|
'arrow-forward',
|
||||||
'arrow-up',
|
'arrow-up',
|
||||||
'briefcase-outline',
|
'briefcase-outline',
|
||||||
'bookmark-outline',
|
'bookmark-outline',
|
||||||
|
|||||||
@@ -304,6 +304,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- string or number -->
|
<!-- string or number -->
|
||||||
<ion-item-group
|
<ion-item-group
|
||||||
|
[id]="getElementId(entry.key, i)"
|
||||||
*ngIf="spec.subtype === 'string' || spec.subtype === 'number'"
|
*ngIf="spec.subtype === 'string' || spec.subtype === 'number'"
|
||||||
>
|
>
|
||||||
<ion-item color="dark">
|
<ion-item color="dark">
|
||||||
|
|||||||
@@ -141,23 +141,25 @@ export class FormObjectComponent {
|
|||||||
|
|
||||||
if (!newItem) return
|
if (!newItem) return
|
||||||
|
|
||||||
|
const index = arr.length
|
||||||
|
|
||||||
newItem.markAllAsTouched()
|
newItem.markAllAsTouched()
|
||||||
arr.insert(0, newItem)
|
arr.insert(index, newItem)
|
||||||
if (['object', 'union'].includes(listSpec.subtype)) {
|
if (['object', 'union'].includes(listSpec.subtype)) {
|
||||||
const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[
|
const displayAs = (listSpec.spec as ListValueSpecOf<'object'>)[
|
||||||
'display-as'
|
'display-as'
|
||||||
]
|
]
|
||||||
this.objectListDisplay[key].unshift({
|
this.objectListDisplay[key].push({
|
||||||
height: '0px',
|
height: '0px',
|
||||||
expanded: true,
|
expanded: true,
|
||||||
displayAs: displayAs ? Mustache.render(displayAs, newItem.value) : '',
|
displayAs: displayAs ? Mustache.render(displayAs, newItem.value) : '',
|
||||||
})
|
})
|
||||||
|
|
||||||
pauseFor(200).then(() => {
|
|
||||||
this.objectListDisplay[key][0].height = this.getDocSize(key, 0)
|
|
||||||
this.onExpand.emit()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pauseFor(400).then(() => {
|
||||||
|
const element = document.getElementById(this.getElementId(key, index))
|
||||||
|
element?.parentElement?.scrollIntoView({ behavior: 'smooth' })
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleExpandObject(key: string) {
|
toggleExpandObject(key: string) {
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
<ion-icon
|
<ion-icon
|
||||||
*ngIf="pkg.error; else bulb"
|
*ngIf="pkg.error; else noError"
|
||||||
class="warning-icon"
|
class="warning-icon"
|
||||||
name="warning-outline"
|
name="warning-outline"
|
||||||
size="small"
|
size="small"
|
||||||
color="warning"
|
color="warning"
|
||||||
></ion-icon>
|
></ion-icon>
|
||||||
|
<ng-template #noError>
|
||||||
|
<ion-spinner
|
||||||
|
*ngIf="pkg.transitioning; else bulb"
|
||||||
|
class="spinner"
|
||||||
|
size="small"
|
||||||
|
color="primary"
|
||||||
|
></ion-spinner>
|
||||||
<ng-template #bulb>
|
<ng-template #bulb>
|
||||||
<div class="bulb" [style.background-color]="color"></div>
|
<div class="bulb" [style.background-color]="color"></div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
</ng-template>
|
||||||
|
|||||||
@@ -18,3 +18,10 @@
|
|||||||
background-color: rgba(255, 213, 52, 0.1);
|
background-color: rgba(255, 213, 52, 0.1);
|
||||||
box-shadow: 0 0 4px 4px rgba(255, 213, 52, 0.1);
|
box-shadow: 0 0 4px 4px rgba(255, 213, 52, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: absolute !important;
|
||||||
|
left: 6px !important;
|
||||||
|
top: 6px !important;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { AlertController } from '@ionic/angular'
|
|
||||||
import {
|
import {
|
||||||
HealthResult,
|
HealthResult,
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
@@ -20,8 +19,6 @@ export class AppShowHealthChecksComponent {
|
|||||||
|
|
||||||
HealthResult = HealthResult
|
HealthResult = HealthResult
|
||||||
|
|
||||||
constructor(private readonly alertCtrl: AlertController) {}
|
|
||||||
|
|
||||||
isLoading(result: HealthResult): boolean {
|
isLoading(result: HealthResult): boolean {
|
||||||
return result === HealthResult.Starting || result === HealthResult.Loading
|
return result === HealthResult.Starting || result === HealthResult.Loading
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import { ValueSpecObject } from 'src/app/pkg-config/config-types'
|
|||||||
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page'
|
||||||
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
import { PatchDbService } from '../../../services/patch-db/patch-db.service'
|
||||||
import { v4 } from 'uuid'
|
import { v4 } from 'uuid'
|
||||||
import { UIMarketplaceData } from '../../../services/patch-db/data-model'
|
import {
|
||||||
|
UIData,
|
||||||
|
UIMarketplaceData,
|
||||||
|
} from '../../../services/patch-db/data-model'
|
||||||
import { ConfigService } from '../../../services/config.service'
|
import { ConfigService } from '../../../services/config.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import {
|
import {
|
||||||
@@ -54,10 +57,10 @@ export class MarketplacesPage {
|
|||||||
this.patch
|
this.patch
|
||||||
.watch$('ui')
|
.watch$('ui')
|
||||||
.pipe(
|
.pipe(
|
||||||
map(ui => ui.marketplace),
|
map((ui: UIData) => ui.marketplace),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
)
|
)
|
||||||
.subscribe(mp => {
|
.subscribe((mp: UIMarketplaceData | undefined) => {
|
||||||
let marketplaces: Marketplaces = [
|
let marketplaces: Marketplaces = [
|
||||||
{
|
{
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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 {
|
import {
|
||||||
ServerInfo,
|
ServerInfo,
|
||||||
|
UIData,
|
||||||
UIMarketplaceData,
|
UIMarketplaceData,
|
||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
import { PatchDbService } from 'src/app/services/patch-db/patch-db.service'
|
||||||
@@ -34,7 +35,7 @@ export class MarketplaceService extends AbstractMarketplaceService {
|
|||||||
private readonly altMarketplaceData$: Observable<
|
private readonly altMarketplaceData$: Observable<
|
||||||
UIMarketplaceData | undefined
|
UIMarketplaceData | undefined
|
||||||
> = this.patch.watch$('ui').pipe(
|
> = this.patch.watch$('ui').pipe(
|
||||||
map(ui => ui.marketplace),
|
map((ui: UIData) => ui.marketplace),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
shareReplay({ bufferSize: 1, refCount: true }),
|
shareReplay({ bufferSize: 1, refCount: true }),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,11 +16,12 @@ export function renderPkgStatus(pkg: PackageDataEntry): PackageStatus {
|
|||||||
let primary: PrimaryStatus
|
let primary: PrimaryStatus
|
||||||
let dependency: DependencyStatus | null = null
|
let dependency: DependencyStatus | null = null
|
||||||
let health: HealthStatus | null = null
|
let health: HealthStatus | null = null
|
||||||
|
const hasHealthChecks = !isEmptyObject(pkg.manifest['health-checks'])
|
||||||
|
|
||||||
if (pkg.state === PackageState.Installed && pkg.installed) {
|
if (pkg.state === PackageState.Installed && pkg.installed) {
|
||||||
primary = getPrimaryStatus(pkg.installed.status)
|
primary = getPrimaryStatus(pkg.installed.status)
|
||||||
dependency = getDependencyStatus(pkg)
|
dependency = getDependencyStatus(pkg)
|
||||||
health = getHealthStatus(pkg.installed.status)
|
health = getHealthStatus(pkg.installed.status, hasHealthChecks)
|
||||||
} else {
|
} else {
|
||||||
primary = pkg.state as string as PrimaryStatus
|
primary = pkg.state as string as PrimaryStatus
|
||||||
}
|
}
|
||||||
@@ -56,19 +57,24 @@ function getHealthStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const values = Object.values(status.main.health)
|
const values = Object.values(status.main.health)
|
||||||
|
console.log('HEALTH CHECKS', values)
|
||||||
|
|
||||||
if (values.some(h => h.result === 'failure')) {
|
if (values.some(h => h.result === 'failure')) {
|
||||||
return HealthStatus.Failure
|
return HealthStatus.Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.some(h => h.result === 'starting')) {
|
if (!values.length && hasHealthChecks) {
|
||||||
return HealthStatus.Starting
|
return HealthStatus.Waiting
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.some(h => h.result === 'loading')) {
|
if (values.some(h => h.result === 'loading')) {
|
||||||
return HealthStatus.Loading
|
return HealthStatus.Loading
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (values.some(h => !h.result || h.result === 'starting')) {
|
||||||
|
return HealthStatus.Starting
|
||||||
|
}
|
||||||
|
|
||||||
return HealthStatus.Healthy
|
return HealthStatus.Healthy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +107,7 @@ export enum DependencyStatus {
|
|||||||
|
|
||||||
export enum HealthStatus {
|
export enum HealthStatus {
|
||||||
Failure = 'failure',
|
Failure = 'failure',
|
||||||
|
Waiting = 'waiting',
|
||||||
Starting = 'starting',
|
Starting = 'starting',
|
||||||
Loading = 'loading',
|
Loading = 'loading',
|
||||||
Healthy = 'healthy',
|
Healthy = 'healthy',
|
||||||
|
|||||||
@@ -12,14 +12,20 @@ import { packageLoadingProgress } from './package-loading-progress'
|
|||||||
|
|
||||||
export function getPackageInfo(entry: PackageDataEntry): PkgInfo {
|
export function getPackageInfo(entry: PackageDataEntry): PkgInfo {
|
||||||
const statuses = renderPkgStatus(entry)
|
const statuses = renderPkgStatus(entry)
|
||||||
|
const primaryRendering = PrimaryRendering[statuses.primary]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
entry,
|
entry,
|
||||||
primaryRendering: PrimaryRendering[statuses.primary],
|
primaryRendering,
|
||||||
installProgress: packageLoadingProgress(entry['install-progress']),
|
installProgress: packageLoadingProgress(entry['install-progress']),
|
||||||
error:
|
error:
|
||||||
statuses.health === HealthStatus.Failure ||
|
statuses.health === HealthStatus.Failure ||
|
||||||
statuses.dependency === DependencyStatus.Warning,
|
statuses.dependency === DependencyStatus.Warning,
|
||||||
|
transitioning:
|
||||||
|
primaryRendering.showDots ||
|
||||||
|
statuses.health === HealthStatus.Waiting ||
|
||||||
|
statuses.health === HealthStatus.Loading ||
|
||||||
|
statuses.health === HealthStatus.Starting,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,5 +34,6 @@ export interface PkgInfo {
|
|||||||
primaryRendering: StatusRendering
|
primaryRendering: StatusRendering
|
||||||
installProgress: ProgressData | null
|
installProgress: ProgressData | null
|
||||||
error: boolean
|
error: boolean
|
||||||
|
transitioning: boolean
|
||||||
sub?: Subscription | null
|
sub?: Subscription | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"/rpc/v1": {
|
"/rpc/v1": {
|
||||||
"target": "http://<CHANGE_ME>/rpc/v1"
|
"target": "http://<CHANGE_ME>/rpc/v1"
|
||||||
},
|
},
|
||||||
"/ws/*": {
|
"/ws/db": {
|
||||||
"target": "http://<CHANGE_ME>",
|
"target": "http://<CHANGE_ME>",
|
||||||
"secure": false,
|
"secure": false,
|
||||||
"ws": true
|
"ws": true
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user