fix dep error display, show starting if any health check starting, show disabled health check message, remove loader from service list, animated dots, better color (#3025)

* refector addresses to not need gateways array

* fix dep error display, show starting if any health check starting, show disabled health check message, remove loader from service list, animated dots, better color

* fix: fix action results textfields

---------

Co-authored-by: waterplea <alexander@inkin.ru>
This commit is contained in:
Matt Hill
2025-09-17 10:32:20 -06:00
committed by GitHub
parent 1d331d7810
commit 7eecf29449
9 changed files with 60 additions and 99 deletions

View File

@@ -110,6 +110,7 @@ body {
animation-fill-mode: forwards; animation-fill-mode: forwards;
text-align: left; text-align: left;
width: 1em; width: 1em;
margin-left: -.3rem;
} }
@keyframes ellipsis-dot { @keyframes ellipsis-dot {

View File

@@ -57,7 +57,7 @@
var(--tui-status-warning) 24%, var(--tui-status-warning) 24%,
transparent transparent
); );
--tui-status-info: rgba(128, 89, 229, 1); --tui-status-info: rgba(53, 96, 240, 1);
--tui-status-info-pale: color-mix( --tui-status-info-pale: color-mix(
in hsl, in hsl,
var(--tui-status-info) 12%, var(--tui-status-info) 12%,

View File

@@ -166,12 +166,10 @@ export class InterfaceService {
}, [] as AddressWithInfo[]) }, [] as AddressWithInfo[])
return { return {
common: bestAddrs.map(a => common: bestAddrs.map(a => this.toDisplayAddress(a, host.publicDomains)),
this.toDisplayAddress(a, gateways, host.publicDomains),
),
uncommon: allAddressesWithInfo uncommon: allAddressesWithInfo
.filter(a => !bestAddrs.includes(a)) .filter(a => !bestAddrs.includes(a))
.map(a => this.toDisplayAddress(a, gateways, host.publicDomains)), .map(a => this.toDisplayAddress(a, host.publicDomains)),
} }
} }
@@ -314,7 +312,6 @@ export class InterfaceService {
private toDisplayAddress( private toDisplayAddress(
{ info, url, gateway }: AddressWithInfo, { info, url, gateway }: AddressWithInfo,
gateways: GatewayPlus[],
publicDomains: Record<string, T.PublicDomainConfig>, publicDomains: Record<string, T.PublicDomainConfig>,
): DisplayAddress { ): DisplayAddress {
let access: DisplayAddress['access'] let access: DisplayAddress['access']
@@ -360,11 +357,11 @@ export class InterfaceService {
// ** Not Tor ** // ** Not Tor **
} else { } else {
const port = info.hostname.sslPort || info.hostname.port const port = info.hostname.sslPort || info.hostname.port
const gateway = gateways.find(g => g.id === info.gateway.id)! const g = gateway!
gatewayName = gateway.name gatewayName = g.name
const gatewayLanIpv4 = gateway.lanIpv4[0] const gatewayLanIpv4 = g.lanIpv4[0]
const isWireguard = gateway.ipInfo.deviceType === 'wireguard' const isWireguard = g.ipInfo.deviceType === 'wireguard'
const localIdeal = this.i18n.transform('Ideal for local access') const localIdeal = this.i18n.transform('Ideal for local access')
const lanRequired = this.i18n.transform( const lanRequired = this.i18n.transform(
@@ -405,9 +402,9 @@ export class InterfaceService {
), ),
rootCaRequired, rootCaRequired,
] ]
if (!gateway.public) { if (!g.public) {
bullets.push( bullets.push(
`${portForwarding} "${gatewayName}": ${port} -> ${gateway.subnets.find(s => s.isIpv4())?.address}:${port}`, `${portForwarding} "${gatewayName}": ${port} -> ${g.subnets.find(s => s.isIpv4())?.address}:${port}`,
) )
} }
} else { } else {
@@ -439,12 +436,12 @@ export class InterfaceService {
if (info.public) { if (info.public) {
access = 'public' access = 'public'
bullets = [ bullets = [
`${dnsFor} ${info.hostname.value} ${resolvesTo} ${gateway.ipInfo.wanIp}`, `${dnsFor} ${info.hostname.value} ${resolvesTo} ${g.ipInfo.wanIp}`,
] ]
if (!gateway.public) { if (!g.public) {
bullets.push( bullets.push(
`${portForwarding} "${gatewayName}": ${port} -> ${gateway.subnets.find(s => s.isIpv4())?.address}:${port === 443 ? 5443 : port}`, `${portForwarding} "${gatewayName}": ${port} -> ${g.subnets.find(s => s.isIpv4())?.address}:${port === 443 ? 5443 : port}`,
) )
} }

View File

@@ -97,10 +97,8 @@ export class ServiceHealthCheckComponent {
return `${this.i18n.transform('Success')}: ${this.healthCheck.message || 'health check passing'}` return `${this.i18n.transform('Success')}: ${this.healthCheck.message || 'health check passing'}`
case 'loading': case 'loading':
case 'failure': case 'failure':
return this.healthCheck.message case 'disabled':
// disabled return this.healthCheck.message || this.healthCheck.result
default:
return this.healthCheck.result
} }
} }
} }

View File

@@ -6,22 +6,26 @@ import {
} from '@angular/core' } from '@angular/core'
import { i18nKey, i18nPipe } from '@start9labs/shared' import { i18nKey, i18nPipe } from '@start9labs/shared'
import { tuiPure } from '@taiga-ui/cdk' import { tuiPure } from '@taiga-ui/cdk'
import { TuiIcon, TuiLoader } from '@taiga-ui/core' import { TuiIcon } from '@taiga-ui/core'
import { getProgressText } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe' import { getProgressText } from 'src/app/routes/portal/routes/services/pipes/install-progress.pipe'
import { PackageDataEntry } from 'src/app/services/patch-db/data-model' import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service' import {
PrimaryRendering,
renderPkgStatus,
} from 'src/app/services/pkg-status-rendering.service'
@Component({ @Component({
selector: 'td[appStatus]', selector: 'td[appStatus]',
template: ` template: `
@if (loading) { @if (!healthy) {
<tui-loader size="s" /> <tui-icon icon="@tui.triangle-alert" class="g-warning" />
} @else { }
@if (!healthy) {
<tui-icon icon="@tui.triangle-alert" class="g-warning" /> <b [style.color]="color">{{ status | i18n }}</b>
}
@if (showDots) {
<span class="loading-dots g-info"></span>
} }
<b [style.color]="color">{{ status | i18n }}{{ dots }}</b>
`, `,
styles: ` styles: `
:host { :host {
@@ -37,7 +41,7 @@ import { renderPkgStatus } from 'src/app/services/pkg-status-rendering.service'
} }
`, `,
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [TuiIcon, TuiLoader, i18nPipe], imports: [TuiIcon, i18nPipe],
}) })
export class StatusComponent { export class StatusComponent {
@Input() @Input()
@@ -58,10 +62,6 @@ export class StatusComponent {
) )
} }
get loading(): boolean {
return this.color === 'var(--tui-status-info)'
}
@tuiPure @tuiPure
getStatus(pkg: PackageDataEntry) { getStatus(pkg: PackageDataEntry) {
return renderPkgStatus(pkg) return renderPkgStatus(pkg)
@@ -72,35 +72,10 @@ export class StatusComponent {
return `${this.i18n.transform('Installing')}... ${this.i18n.transform(getProgressText(this.pkg.stateInfo.installingInfo.progress.overall))}` as i18nKey return `${this.i18n.transform('Installing')}... ${this.i18n.transform(getProgressText(this.pkg.stateInfo.installingInfo.progress.overall))}` as i18nKey
} }
switch (this.getStatus(this.pkg).primary) { return PrimaryRendering[this.getStatus(this.pkg).primary].display
case 'running':
return 'Running'
case 'stopped':
return 'Stopped'
case 'taskRequired':
return 'Task Required'
case 'updating':
return 'Updating'
case 'stopping':
return 'Stopping'
case 'starting':
return 'Starting'
case 'backingUp':
return 'Backing Up'
case 'restarting':
return 'Restarting'
case 'removing':
return 'Removing'
case 'restoring':
return 'Restoring'
case 'error':
return 'Error'
default:
return 'Unknown'
}
} }
get dots(): '...' | '' { get showDots() {
switch (this.getStatus(this.pkg).primary) { switch (this.getStatus(this.pkg).primary) {
case 'updating': case 'updating':
case 'stopping': case 'stopping':
@@ -108,9 +83,9 @@ export class StatusComponent {
case 'backingUp': case 'backingUp':
case 'restarting': case 'restarting':
case 'removing': case 'removing':
return '...' return true
default: default:
return '' return false
} }
} }

View File

@@ -27,7 +27,6 @@ import { QrCodeComponent } from 'ng-qrcode'
tuiTextfield tuiTextfield
[readOnly]="true" [readOnly]="true"
[ngModel]="member.value" [ngModel]="member.value"
[style.border-inline-end-width.rem]="border"
[type]="member.masked && masked ? 'password' : 'text'" [type]="member.masked && masked ? 'password' : 'text'"
/> />
@if (member.masked) { @if (member.masked) {
@@ -129,16 +128,6 @@ export class ActionSuccessMemberComponent {
masked = true masked = true
get border(): number {
let border = 0
if (this.member.masked) border += 2
if (this.member.copyable) border += 2
if (this.member.qr) border += 2
return border
}
show(template: TemplateRef<any>) { show(template: TemplateRef<any>) {
const masked = this.masked const masked = this.masked

View File

@@ -23,7 +23,6 @@ import { SingleResult } from './types'
tuiTextfield tuiTextfield
[readOnly]="true" [readOnly]="true"
[ngModel]="single.value" [ngModel]="single.value"
[style.border-inline-end-width.rem]="border"
[type]="single.masked && masked ? 'password' : 'text'" [type]="single.masked && masked ? 'password' : 'text'"
/> />
@if (single.masked) { @if (single.masked) {
@@ -105,15 +104,6 @@ export class ActionSuccessSingleComponent {
masked = true masked = true
get border(): number {
let border = 0
if (this.single.masked) border += 2
if (this.single.copyable) border += 2
return border
}
copy() { copy() {
const el = this.input.nativeElement const el = this.input.nativeElement

View File

@@ -126,13 +126,14 @@ export class DepErrorService {
const expected = currentDep?.versionRange || '' const expected = currentDep?.versionRange || ''
// incorrect version // incorrect version
if (!this.exver.satisfies(depManifest.version, expected)) { if (
if (depManifest.satisfies.some(v => !this.exver.satisfies(v, expected))) { !this.exver.satisfies(depManifest.version, expected) &&
return { !depManifest.satisfies.some(v => this.exver.satisfies(v, expected))
expected, ) {
type: 'incorrectVersion', return {
received: depManifest.version, expected,
} type: 'incorrectVersion',
received: depManifest.version,
} }
} }

View File

@@ -25,11 +25,21 @@ export function getInstalledPrimaryStatus({
tasks, tasks,
status, status,
}: T.PackageDataEntry): PrimaryStatus { }: T.PackageDataEntry): PrimaryStatus {
return Object.values(tasks).some( if (
t => t.active && t.task.severity === 'critical', Object.values(tasks).some(t => t.active && t.task.severity === 'critical')
) ) {
? 'taskRequired' return 'taskRequired'
: status.main }
if (
Object.values(status.main === 'running' && status.health)
.filter(h => !!h)
.some(h => h.result === 'starting')
) {
return 'starting'
}
return status.main
} }
function getHealthStatus(status: T.MainStatus): T.HealthStatus | null { function getHealthStatus(status: T.MainStatus): T.HealthStatus | null {
@@ -43,14 +53,14 @@ function getHealthStatus(status: T.MainStatus): T.HealthStatus | null {
return 'failure' return 'failure'
} }
if (values.some(h => h.result === 'loading')) {
return 'loading'
}
if (values.some(h => h.result === 'starting')) { if (values.some(h => h.result === 'starting')) {
return 'starting' return 'starting'
} }
if (values.some(h => h.result === 'loading')) {
return 'loading'
}
return 'success' return 'success'
} }