feat: split row_actions into remove_action and overflow_actions for URL plugins

This commit is contained in:
Aiden McClelland
2026-02-18 18:18:53 -07:00
parent 9c3053f103
commit d562466fc4
9 changed files with 119 additions and 48 deletions

View File

@@ -278,7 +278,7 @@ core/bindings/index.ts: $(call ls-files, core) $(ENVIRONMENT_FILE)
rm -rf core/bindings
./core/build/build-ts.sh
ls core/bindings/*.ts | sed 's/core\/bindings\/\([^.]*\)\.ts/export { \1 } from ".\/\1";/g' | grep -v '"./index"' | tee core/bindings/index.ts
npm --prefix sdk exec -- prettier --config ./sdk/base/package.json -w ./core/bindings/*.ts
npm --prefix sdk/base exec -- prettier --config=./sdk/base/package.json -w ./core/bindings/*.ts
touch core/bindings/index.ts
sdk/dist/package.json sdk/baseDist/package.json: $(call ls-files, sdk) sdk/base/lib/osBindings/index.ts

View File

@@ -43,7 +43,8 @@ pub enum HostnameMetadata {
},
Plugin {
package_id: PackageId,
row_actions: Vec<ActionId>,
remove_action: Option<ActionId>,
overflow_actions: Vec<ActionId>,
#[ts(type = "unknown")]
#[serde(default)]
info: Value,
@@ -99,7 +100,8 @@ impl PluginHostnameInfo {
pub fn to_hostname_info(
&self,
plugin_package: &PackageId,
row_actions: Vec<ActionId>,
remove_action: Option<ActionId>,
overflow_actions: Vec<ActionId>,
) -> HostnameInfo {
HostnameInfo {
ssl: self.ssl,
@@ -109,7 +111,8 @@ impl PluginHostnameInfo {
metadata: HostnameMetadata::Plugin {
package_id: plugin_package.clone(),
info: self.info.clone(),
row_actions,
remove_action,
overflow_actions,
},
}
}
@@ -118,7 +121,9 @@ impl PluginHostnameInfo {
/// (comparing address fields only, not row_actions).
pub fn matches_hostname_info(&self, h: &HostnameInfo, plugin_package: &PackageId) -> bool {
match &h.metadata {
HostnameMetadata::Plugin { package_id, info, .. } => {
HostnameMetadata::Plugin {
package_id, info, ..
} => {
package_id == plugin_package
&& h.ssl == self.ssl
&& h.public == self.public

View File

@@ -19,7 +19,10 @@ fn require_url_plugin(context: &Arc<Service>) -> Result<(), Error> {
.contains(&PluginId::UrlV0)
{
return Err(Error::new(
eyre!("{}", t!("net.plugin.manifest-missing-plugin", plugin = "url-v0")),
eyre!(
"{}",
t!("net.plugin.manifest-missing-plugin", plugin = "url-v0")
),
ErrorKind::InvalidRequest,
));
}
@@ -68,36 +71,45 @@ pub async fn register(
#[ts(export)]
pub struct UrlPluginExportUrlParams {
pub hostname_info: PluginHostnameInfo,
pub row_actions: Vec<ActionId>,
pub remove_action: Option<ActionId>,
pub overflow_actions: Vec<ActionId>,
}
pub async fn export_url(
context: EffectContext,
UrlPluginExportUrlParams {
hostname_info,
row_actions,
remove_action,
overflow_actions,
}: UrlPluginExportUrlParams,
) -> Result<(), Error> {
let context = context.deref()?;
require_url_plugin(&context)?;
let plugin_id = context.seed.id.clone();
let entry = hostname_info.to_hostname_info(&plugin_id, row_actions);
let entry = hostname_info.to_hostname_info(&plugin_id, remove_action, overflow_actions);
context
.seed
.ctx
.db
.mutate(|db| {
let host = host_for(db, hostname_info.package_id.as_ref(), &hostname_info.host_id)?;
let host = host_for(
db,
hostname_info.package_id.as_ref(),
&hostname_info.host_id,
)?;
host.as_bindings_mut()
.as_idx_mut(&hostname_info.internal_port)
.or_not_found(t!("net.plugin.binding-not-found", binding = format!(
"{}:{}:{}",
hostname_info.package_id.as_deref().unwrap_or("STARTOS"),
hostname_info.host_id,
hostname_info.internal_port
)))?
.or_not_found(t!(
"net.plugin.binding-not-found",
binding = format!(
"{}:{}:{}",
hostname_info.package_id.as_deref().unwrap_or("STARTOS"),
hostname_info.host_id,
hostname_info.internal_port
)
))?
.as_addresses_mut()
.as_available_mut()
.mutate(|available: &mut BTreeSet<_>| {

View File

@@ -159,7 +159,8 @@ export type Effects = {
register(options: { tableAction: ActionId }): Promise<null>
exportUrl(options: {
hostnameInfo: PluginHostnameInfo
rowActions: ActionId[]
removeAction: ActionId | null
overflowActions: ActionId[]
}): Promise<null>
clearUrls(options: { except: PluginHostnameInfo[] }): Promise<null>
}

View File

@@ -12,6 +12,7 @@ export type HostnameMetadata =
| {
kind: 'plugin'
packageId: PackageId
rowActions: Array<ActionId>
removeAction: ActionId | null
overflowActions: Array<ActionId>
info: unknown
}

View File

@@ -4,5 +4,6 @@ import type { PluginHostnameInfo } from './PluginHostnameInfo'
export type UrlPluginExportUrlParams = {
hostnameInfo: PluginHostnameInfo
rowActions: Array<ActionId>
removeAction: ActionId | null
overflowActions: Array<ActionId>
}

View File

@@ -765,7 +765,15 @@ export class StartSdk<Manifest extends T.SDKManifest> {
effects: T.Effects,
options: {
hostnameInfo: T.PluginHostnameInfo
rowActions: ActionInfo<
removeAction: ActionInfo<
T.ActionId,
{
urlPluginMetadata: T.PluginHostnameInfo & {
interfaceId: T.ServiceInterfaceId
}
}
> | null
overflowActions: ActionInfo<
T.ActionId,
{
urlPluginMetadata: T.PluginHostnameInfo & {
@@ -777,7 +785,8 @@ export class StartSdk<Manifest extends T.SDKManifest> {
) =>
effects.plugin.url.exportUrl({
hostnameInfo: options.hostnameInfo,
rowActions: options.rowActions.map((a) => a.id),
removeAction: options.removeAction?.id ?? null,
overflowActions: options.overflowActions.map((a) => a.id),
}),
setupExportedUrls, // similar to setupInterfaces
}),

View File

@@ -5,11 +5,7 @@ import {
input,
signal,
} from '@angular/core'
import {
CopyService,
DialogService,
i18nPipe,
} from '@start9labs/shared'
import { CopyService, DialogService, i18nPipe } from '@start9labs/shared'
import { T } from '@start9labs/start-sdk'
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
import {
@@ -72,21 +68,30 @@ import {
{{ 'Copy URL' | i18n }}
</button>
@if (address.hostnameInfo.metadata.kind === 'plugin') {
@for (
actionId of address.hostnameInfo.metadata.rowActions;
track actionId
) {
@if (pluginGroup().pluginActions[actionId]; as meta) {
@if (address.hostnameInfo.metadata.removeAction) {
@if (
pluginGroup().pluginActions[
address.hostnameInfo.metadata.removeAction
];
as meta
) {
<button
tuiIconButton
appearance="flat-grayscale"
iconStart="@tui.play"
(click)="runRowAction(actionId, meta, address)"
iconStart="@tui.trash"
(click)="
runRowAction(
address.hostnameInfo.metadata.removeAction,
meta,
address
)
"
>
{{ meta.name }}
</button>
}
}
<!-- TODO @MattHill: overflow -->
}
</div>
<div class="mobile">
@@ -117,21 +122,30 @@ import {
{{ 'Copy URL' | i18n }}
</button>
@if (address.hostnameInfo.metadata.kind === 'plugin') {
@for (
actionId of address.hostnameInfo.metadata.rowActions;
track actionId
) {
@if (pluginGroup().pluginActions[actionId]; as meta) {
@if (address.hostnameInfo.metadata.removeAction) {
@if (
pluginGroup().pluginActions[
address.hostnameInfo.metadata.removeAction
];
as meta
) {
<button
tuiOption
new
iconStart="@tui.play"
(click)="runRowAction(actionId, meta, address)"
iconStart="@tui.trash"
(click)="
runRowAction(
address.hostnameInfo.metadata.removeAction,
meta,
address
)
"
>
{{ meta.name }}
</button>
}
}
<!-- TODO @MattHill: overflow -->
}
</tui-data-list>
</button>

View File

@@ -94,16 +94,30 @@ export const mockPatchData: DataModel = {
{
ssl: false,
public: false,
hostname: 'abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567abc.onion',
hostname:
'abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567abc.onion',
port: 80,
metadata: { kind: 'plugin', packageId: 'tor', rowActions: [], info: null },
metadata: {
kind: 'plugin',
packageId: 'tor',
removeAction: 'delete-onion-service',
overflowActions: [],
info: null,
},
},
{
ssl: true,
public: false,
hostname: 'abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567abc.onion',
hostname:
'abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567abc.onion',
port: 443,
metadata: { kind: 'plugin', packageId: 'tor', rowActions: [], info: null },
metadata: {
kind: 'plugin',
packageId: 'tor',
removeAction: 'delete-onion-service',
overflowActions: [],
info: null,
},
},
],
},
@@ -595,16 +609,30 @@ export const mockPatchData: DataModel = {
{
ssl: false,
public: false,
hostname: 'xyz789abc123def456ghi789jkl012mno345pqr678stu901vwx234.onion',
hostname:
'xyz789abc123def456ghi789jkl012mno345pqr678stu901vwx234.onion',
port: 42080,
metadata: { kind: 'plugin', packageId: 'tor', rowActions: [], info: null },
metadata: {
kind: 'plugin',
packageId: 'tor',
removeAction: 'delete-onion-service',
overflowActions: [],
info: null,
},
},
{
ssl: true,
public: false,
hostname: 'xyz789abc123def456ghi789jkl012mno345pqr678stu901vwx234.onion',
hostname:
'xyz789abc123def456ghi789jkl012mno345pqr678stu901vwx234.onion',
port: 42443,
metadata: { kind: 'plugin', packageId: 'tor', rowActions: [], info: null },
metadata: {
kind: 'plugin',
packageId: 'tor',
removeAction: 'delete-onion-service',
overflowActions: [],
info: null,
},
},
],
},