chore: bump sdk to beta.54, add device-info RPC, improve SDK abort handling and InputSpec filtering

- Bump SDK version to 0.4.0-beta.54
- Add `server.device-info` RPC endpoint and `s9pk select` CLI command
- Extract `HardwareRequirements::is_compatible()` method, reuse in registry filtering
- Add `AbortedError` class with `muteUnhandled` flag, replace generic abort errors
- Handle unhandled promise rejections in container-runtime with mute support
- Improve `InputSpec.filter()` with `keepByDefault` param and boolean filter values
- Accept readonly tuples in `CommandType` and `splitCommand`
- Remove `sync_host` calls from host API handlers (binding/address changes)
- Filter mDNS hostnames by secure gateway availability
- Derive mDNS enabled state from LAN IPs in web UI
- Add "Open UI" action to address table, disable mDNS toggle
- Hide debug details in service error component
- Update rpc-toolkit docs for no-params handlers
This commit is contained in:
Aiden McClelland
2026-02-26 14:08:33 -07:00
parent 7f66c62848
commit d422cd3c66
33 changed files with 379 additions and 117 deletions

View File

@@ -30,6 +30,19 @@ import { DomainHealthService } from './domain-health.service'
selector: 'td[actions]',
template: `
<div class="desktop">
@if (address().ui) {
<a
tuiIconButton
appearance="flat-grayscale"
iconStart="@tui.external-link"
target="_blank"
rel="noreferrer"
[attr.href]="address().enabled ? address().url : null"
[class.disabled]="!address().enabled"
>
{{ 'Open UI' | i18n }}
</a>
}
@if (address().deletable) {
<button
tuiIconButton
@@ -102,6 +115,19 @@ import { DomainHealthService } from './domain-health.service'
>
{{ 'Actions' | i18n }}
<tui-data-list *tuiTextfieldDropdown (click)="open.set(false)">
@if (address().ui) {
<a
tuiOption
new
iconStart="@tui.external-link"
target="_blank"
rel="noreferrer"
[attr.href]="address().enabled ? address().url : null"
[class.disabled]="!address().enabled"
>
{{ 'Open UI' | i18n }}
</a>
}
<button
tuiOption
new
@@ -179,6 +205,11 @@ import { DomainHealthService } from './domain-health.service'
white-space: nowrap;
}
.disabled {
pointer-events: none;
opacity: var(--tui-disabled-opacity);
}
.mobile {
display: none;
}

View File

@@ -29,7 +29,7 @@ import { DomainHealthService } from './domain-health.service'
tuiSwitch
size="s"
[showIcons]="false"
[disabled]="toggling()"
[disabled]="toggling() || address.hostnameInfo.metadata.kind === 'mdns'"
[ngModel]="address.enabled"
(ngModelChange)="onToggleEnabled()"
/>

View File

@@ -13,6 +13,10 @@ function isPublicIp(h: T.HostnameInfo): boolean {
return h.public && (h.metadata.kind === 'ipv4' || h.metadata.kind === 'ipv6')
}
export function isLanIp(h: T.HostnameInfo): boolean {
return !h.public && (h.metadata.kind === 'ipv4' || h.metadata.kind === 'ipv6')
}
function isEnabled(addr: T.DerivedAddressInfo, h: T.HostnameInfo): boolean {
if (isPublicIp(h)) {
if (h.port === null) return true
@@ -135,11 +139,23 @@ export class InterfaceService {
return gateways
.filter(g => (groupMap.get(g.id)?.length ?? 0) > 0)
.map(g => ({
gatewayId: g.id,
gatewayName: g.name,
addresses: groupMap.get(g.id)!.sort(sortDomainsFirst),
}))
.map(g => {
const addresses = groupMap.get(g.id)!.sort(sortDomainsFirst)
// Derive mDNS enabled state from LAN IPs on this gateway
const lanIps = addresses.filter(a => isLanIp(a.hostnameInfo))
for (const a of addresses) {
if (a.hostnameInfo.metadata.kind === 'mdns') {
a.enabled = lanIps.some(ip => ip.enabled)
}
}
return {
gatewayId: g.id,
gatewayName: g.name,
addresses,
}
})
}
getPluginGroups(

View File

@@ -16,7 +16,6 @@ import { getManifest } from 'src/app/utils/get-package-data'
template: `
<header>{{ 'Service Launch Error' | i18n }}</header>
<p class="error-message">{{ error?.details }}</p>
<p>{{ error?.debug }}</p>
<h4>
{{ 'Actions' | i18n }}
<tui-icon [tuiTooltip]="hint" />