task fix and keyboard fix (#3130)

* task fix and keyboard fix

* fixes for build scripts

* passthrough feature

* feat: inline domain health checks and improve address UX

- addPublicDomain returns DNS query + port check results (AddPublicDomainRes)
  so frontend skips separate API calls after adding a domain
- addPrivateDomain returns check_dns result for the gateway
- Support multiple ports per domain in validation modal (deduplicated)
- Run port checks concurrently via futures::future::join_all
- Add note to add-domain dialog showing other interfaces on same host
- Add addXForwardedHeaders to knownProtocols in SDK Host.ts
- Add plugin filter kind, pluginId filter, matchesAny, and docs to
  getServiceInterface.ts
- Add PassthroughInfo type and passthroughs field to NetworkInfo
- Pluralize "port forwarding rules" in i18n dictionaries

* feat: add shared host note to private domain dialog with i18n

* fix: scope public domain to single binding and return single port check

Accept internalPort in AddPublicDomainParams to target a specific
binding. Disable the domain on all other bindings. Return a single
CheckPortRes instead of Vec. Revert multi-port UI to singular port
display from 0f8a66b35.

* better shared hostname approach,  and improve look-feel of addresses tables

* fix starttls

* preserve usb as top efi boot option

* fix race condition in wan ip check

* sdk beta.56

* various bug, improve smtp

* multiple bugs, better outbound gateway UX

* remove non option from smtp for better package compat

* bump sdk

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2026-03-06 00:30:06 -07:00
committed by GitHub
parent 3320391fcc
commit 8b89f016ad
72 changed files with 2075 additions and 759 deletions

View File

@@ -141,6 +141,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
| 'getSystemSmtp'
| 'getOutboundGateway'
| 'getContainerIp'
| 'getStatus'
| 'getDataVersion'
| 'setDataVersion'
| 'getServiceManifest'
@@ -164,7 +165,6 @@ export class StartSdk<Manifest extends T.SDKManifest> {
getSslKey: (effects, ...args) => effects.getSslKey(...args),
shutdown: (effects, ...args) => effects.shutdown(...args),
getDependencies: (effects, ...args) => effects.getDependencies(...args),
getStatus: (effects, ...args) => effects.getStatus(...args),
setHealth: (effects, ...args) => effects.setHealth(...args),
}
@@ -342,6 +342,104 @@ export class StartSdk<Manifest extends T.SDKManifest> {
}
},
/**
* Get the service's current status with reactive subscription support.
*
* Returns an object with multiple read strategies: `const()` for a value
* that retries on change, `once()` for a single read, `watch()` for an async
* generator, `onChange()` for a callback, and `waitFor()` to block until a predicate is met.
*
* @param effects - The effects context
* @param options - Optional filtering options (e.g. `packageId`)
*/
getStatus: (
effects: T.Effects,
options: Omit<Parameters<T.Effects['getStatus']>[0], 'callback'> = {},
) => {
async function* watch(abort?: AbortSignal) {
const resolveCell = { resolve: () => {} }
effects.onLeaveContext(() => {
resolveCell.resolve()
})
abort?.addEventListener('abort', () => resolveCell.resolve())
while (effects.isInContext && !abort?.aborted) {
let callback: () => void = () => {}
const waitForNext = new Promise<void>((resolve) => {
callback = resolve
resolveCell.resolve = resolve
})
yield await effects.getStatus({ ...options, callback })
await waitForNext
}
}
return {
const: () =>
effects.getStatus({
...options,
callback:
effects.constRetry &&
(() => effects.constRetry && effects.constRetry()),
}),
once: () => effects.getStatus(options),
watch: (abort?: AbortSignal) => {
const ctrl = new AbortController()
abort?.addEventListener('abort', () => ctrl.abort())
return DropGenerator.of(watch(ctrl.signal), () => ctrl.abort())
},
onChange: (
callback: (
value: T.StatusInfo | null,
error?: Error,
) => { cancel: boolean } | Promise<{ cancel: boolean }>,
) => {
;(async () => {
const ctrl = new AbortController()
for await (const value of watch(ctrl.signal)) {
try {
const res = await callback(value)
if (res.cancel) {
ctrl.abort()
break
}
} catch (e) {
console.error(
'callback function threw an error @ getStatus.onChange',
e,
)
}
}
})()
.catch((e) => callback(null, e))
.catch((e) =>
console.error(
'callback function threw an error @ getStatus.onChange',
e,
),
)
},
waitFor: async (pred: (value: T.StatusInfo | null) => boolean) => {
const resolveCell = { resolve: () => {} }
effects.onLeaveContext(() => {
resolveCell.resolve()
})
while (effects.isInContext) {
let callback: () => void = () => {}
const waitForNext = new Promise<void>((resolve) => {
callback = resolve
resolveCell.resolve = resolve
})
const res = await effects.getStatus({ ...options, callback })
if (pred(res)) {
resolveCell.resolve()
return res
}
await waitForNext
}
return null
},
}
},
MultiHost: {
/**
* Create a new MultiHost instance for binding ports and exporting interfaces.