From 6ee6879d90f262cc87f17cf379e2699d6fc19c3c Mon Sep 17 00:00:00 2001 From: Matt Hill <9935159+MattDHill@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:21:36 -0600 Subject: [PATCH] feat(start-os): open an action result's link in a new tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `launchable` to a `single` action result value. When set, the UI puts an open-in-new-tab button beside the value, using the same external-link icon the service controls use for "Open UI", so a result that hands the user a URL — an authorization link, an admin panel — can be clicked straight through instead of copied and pasted. `copyable`, `qr` and `masked` become optional at the same time. Making `launchable` required would mean touching every package in the fleet, and the three flags it sits beside should read the same way. Closes #3218 Co-Authored-By: Claude Opus 5 (1M context) --- projects/start-os/CHANGELOG.md | 4 +++ .../action-success-member.component.ts | 17 +++++++++++- .../action-success-single.component.ts | 15 +++++++++++ .../ui/src/app/services/api/api.fixures.ts | 1 + projects/start-sdk/CHANGELOG.md | 6 +++++ projects/start-sdk/docs/src/actions.md | 20 +++++++++++--- shared-libs/crates/start-core/src/action.rs | 27 ++++++++++++------- .../lib/osBindings/ActionResultMember.ts | 16 ++++++----- .../lib/osBindings/ActionResultValue.ts | 16 ++++++----- 9 files changed, 96 insertions(+), 26 deletions(-) diff --git a/projects/start-os/CHANGELOG.md b/projects/start-os/CHANGELOG.md index ed2f7a0de..ab17eba6c 100644 --- a/projects/start-os/CHANGELOG.md +++ b/projects/start-os/CHANGELOG.md @@ -61,6 +61,10 @@ file tracks notable changes since the move to the monorepo. plaintext address, including the passwords typed into it. See [Gateways](https://docs.start9.com/start-os/gateways.html). +- **An action result that hands you a link can be opened in a new tab.** Where a + service returns a URL — an authorization link, an admin panel — the result + shows an open-in-new-tab button beside it. + ### Changed - **Your server's name is now its `.local` address, without the `.local` on the diff --git a/projects/start-os/web/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts b/projects/start-os/web/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts index 0313395d0..8570c6336 100644 --- a/projects/start-os/web/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts +++ b/projects/start-os/web/ui/src/app/routes/portal/routes/services/modals/action-success/action-success-member.component.ts @@ -44,6 +44,21 @@ import { QRComponent } from 'src/app/routes/portal/components/qr.component' {{ 'Copy' | i18n }} } + @if (member.launchable) { + + {{ 'Open' | i18n }} + + } @if (member.qr) { } + @if (single.launchable) { + + {{ 'Open' | i18n }} + + } Self::V1(a), @@ -192,12 +193,18 @@ pub enum ActionResultValue { /// The actual string value to display. The UI renders it as a single-line field — /// multi-line text belongs in the result's `message`. value: String, - /// Whether or not to include a copy to clipboard icon to copy the value - copyable: bool, - /// Whether or not to also display the value as a QR code - qr: bool, - /// Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information - masked: bool, + /// (optional) Whether or not to include a copy to clipboard icon to copy the value + #[ts(optional)] + copyable: Option, + /// (optional) Whether or not to also display the value as a QR code + #[ts(optional)] + qr: Option, + /// (optional) Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information + #[ts(optional)] + masked: Option, + /// (optional) Whether or not to include an open in new tab icon to launch the value, which must be an http(s) URL + #[ts(optional)] + launchable: Option, }, Group { /// An new group of nested values, experienced by the user as an accordion dropdown @@ -212,7 +219,7 @@ impl ActionResultValue { write!(f, " ")?; } write!(f, "{value}")?; - if *qr { + if qr.unwrap_or_default() { use qrcode::render::unicode; writeln!(f)?; for _ in 0..indent { diff --git a/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultMember.ts b/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultMember.ts index 9f3a28973..b8c5ee5c6 100644 --- a/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultMember.ts +++ b/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultMember.ts @@ -18,17 +18,21 @@ export type ActionResultMember = { */ value: string /** - * Whether or not to include a copy to clipboard icon to copy the value + * (optional) Whether or not to include a copy to clipboard icon to copy the value */ - copyable: boolean + copyable?: boolean /** - * Whether or not to also display the value as a QR code + * (optional) Whether or not to also display the value as a QR code */ - qr: boolean + qr?: boolean /** - * Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information + * (optional) Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information */ - masked: boolean + masked?: boolean + /** + * (optional) Whether or not to include an open in new tab icon to launch the value, which must be an http(s) URL + */ + launchable?: boolean } | { type: 'group' diff --git a/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultValue.ts b/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultValue.ts index fa4c733f7..dc2ce28ea 100644 --- a/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultValue.ts +++ b/shared-libs/ts-modules/start-core/lib/osBindings/ActionResultValue.ts @@ -10,17 +10,21 @@ export type ActionResultValue = */ value: string /** - * Whether or not to include a copy to clipboard icon to copy the value + * (optional) Whether or not to include a copy to clipboard icon to copy the value */ - copyable: boolean + copyable?: boolean /** - * Whether or not to also display the value as a QR code + * (optional) Whether or not to also display the value as a QR code */ - qr: boolean + qr?: boolean /** - * Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information + * (optional) Whether or not to mask the value using ●●●●●●●, which is useful for password or other sensitive information */ - masked: boolean + masked?: boolean + /** + * (optional) Whether or not to include an open in new tab icon to launch the value, which must be an http(s) URL + */ + launchable?: boolean } | { type: 'group'